diff --git a/lib/ocelot-brain b/lib/ocelot-brain index f326a45..95f0416 160000 --- a/lib/ocelot-brain +++ b/lib/ocelot-brain @@ -1 +1 @@ -Subproject commit f326a45a0ae223904869ae02ad22ca7afdeb8b1f +Subproject commit 95f04168b920306c066c47308d91b411f95c82a2 diff --git a/src/main/scala/ocelot/desktop/OcelotDesktop.scala b/src/main/scala/ocelot/desktop/OcelotDesktop.scala index 48a1c2e..3187f6d 100644 --- a/src/main/scala/ocelot/desktop/OcelotDesktop.scala +++ b/src/main/scala/ocelot/desktop/OcelotDesktop.scala @@ -3,6 +3,8 @@ package ocelot.desktop import li.flor.nativejfilechooser.NativeJFileChooser import ocelot.desktop.audio.{Audio, SoundSource} import ocelot.desktop.ui.UiHandler +import ocelot.desktop.ui.widget.inventory.item.traits.PersistableItemPrototype +import ocelot.desktop.ui.widget.inventory.item._ import ocelot.desktop.ui.widget.{ExitConfirmationDialog, RootWidget, SettingsDialog} import ocelot.desktop.util.FileUtils.getOcelotConfigDirectory import ocelot.desktop.util._ @@ -11,6 +13,7 @@ import org.apache.logging.log4j.LogManager import totoro.ocelot.brain.Ocelot import totoro.ocelot.brain.event.FileSystemActivityType.Floppy import totoro.ocelot.brain.event._ +import totoro.ocelot.brain.nbt.persistence.NBTPersistence import totoro.ocelot.brain.nbt.{CompressedStreamTools, NBTTagCompound} import totoro.ocelot.brain.workspace.Workspace @@ -31,12 +34,13 @@ object OcelotDesktop extends Logging { private val random: Random = new Random() - def withTickLockAcquired(f: () => Unit): Unit = withLockAcquired(tickLock, f) + def withTickLockAcquired(f: => Unit): Unit = withLockAcquired(tickLock, f) def mainInner(): Unit = { logger.info("Starting up Ocelot Desktop") Ocelot.initialize(LogManager.getLogger(Ocelot)) + setupNbtConstructors() val settingsFile = getOcelotConfigDirectory.resolve("ocelot.conf") Settings.load(settingsFile) @@ -60,10 +64,10 @@ object OcelotDesktop extends Logging { while (true) { Profiler.startTimeMeasurement("tick") - withTickLockAcquired(() => { + withTickLockAcquired { workspace.update() tpsCounter.tick() - }) + } Profiler.endTimeMeasurement("tick") ticker.waitNext() @@ -97,26 +101,26 @@ object OcelotDesktop extends Logging { } } - private def saveWorld(nbt: NBTTagCompound): Unit = withTickLockAcquired(() => { + private def saveWorld(nbt: NBTTagCompound): Unit = withTickLockAcquired { val backendNBT = new NBTTagCompound val frontendNBT = new NBTTagCompound workspace.save(backendNBT) root.workspaceView.save(frontendNBT) nbt.setTag("back", backendNBT) nbt.setTag("front", frontendNBT) - }) + } - private def loadWorld(nbt: NBTTagCompound): Unit = withTickLockAcquired(() => { + private def loadWorld(nbt: NBTTagCompound): Unit = withTickLockAcquired { val backendNBT = nbt.getCompoundTag("back") val frontendNBT = nbt.getCompoundTag("front") workspace.load(backendNBT) root.workspaceView.load(frontendNBT) - }) + } private var savePath: Option[Path] = None - private val tmpPath = Files.createTempDirectory("ocelot-save") + private val tmpPath = Files.createTempDirectory("ocelot-save") def newWorkspace(): Unit = { root.workspaceView.newWorkspace() Settings.get.recentWorkspace = None @@ -252,32 +256,76 @@ object OcelotDesktop extends Logging { workspace = new Workspace(tmpPath) } + def setupNbtConstructors(): Unit = { + // FIXME: must construct and load an item's prototype first and use it then to build the item itself + val itemConstructor = new PersistableItemPrototype.ItemConstructor + val tieredConstructor = new NBTPersistence.TieredConstructor + NBTPersistence.registerConstructor(classOf[CPUItemPrototype.CPU#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[CPUItemPrototype.CPU].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[CPUItemPrototype.APU#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[CPUItemPrototype.APU].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[DataCardItemPrototype.Tier1#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[DataCardItemPrototype.Tier2#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[DataCardItemPrototype.Tier3#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[EEPROMItemPrototype#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[FloppyItemPrototype.Managed#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[FloppyItemPrototype.Unmanaged#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[GraphicsCardItemPrototype#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[GraphicsCardItemPrototype].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[HDDItemPrototype.Managed#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[HDDItemPrototype.Managed].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[HDDItemPrototype.Unmanaged#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[HDDItemPrototype.Unmanaged].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[InternetCardItemPrototype#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[LinkedCardItemPrototype#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[MemoryItemPrototype#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[MemoryItemPrototype].getName, tieredConstructor) + + NBTPersistence.registerConstructor(classOf[NetworkCardItemPrototype#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[RedstoneCardItemPrototype.Tier1#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[RedstoneCardItemPrototype.Tier2#Item].getName, itemConstructor) + + NBTPersistence.registerConstructor(classOf[WirelessNetworkCardItemPrototype.Tier1#Item].getName, itemConstructor) + NBTPersistence.registerConstructor(classOf[WirelessNetworkCardItemPrototype.Tier2#Item].getName, itemConstructor) + } + private def setupEventHandlers(): Unit = { - EventBus.listenTo(classOf[BeepEvent], { case event: BeepEvent => + EventBus.subscribe[BeepEvent] { event => if (!UiHandler.audioDisabled) Audio.beep(event.frequency, event.duration) else logger.info(s"Beep ${event.frequency} Hz for ${event.duration} ms") - }) + } - EventBus.listenTo(classOf[BeepPatternEvent], { case event: BeepPatternEvent => + EventBus.subscribe[BeepPatternEvent] { event => if (!UiHandler.audioDisabled) Audio.beep(event.pattern) else logger.info(s"Beep pattern `${event.pattern}``") - }) + } - EventBus.listenTo(classOf[MachineCrashEvent], { case event: MachineCrashEvent => + EventBus.subscribe[MachineCrashEvent] { event => logger.info(s"[EVENT] Machine crash! (address = ${event.address}, ${event.message})") - }) + } if (!UiHandler.audioDisabled) { val soundFloppyAccess = Audio.FloppyAccess.map(buffer => new SoundSource(buffer)) val soundHDDAccess = Audio.HDDAccess.map(buffer => new SoundSource(buffer)) - EventBus.listenTo(classOf[FileSystemActivityEvent], { case event: FileSystemActivityEvent => + EventBus.subscribe[FileSystemActivityEvent] { event => val sound = if (event.activityType == Floppy) soundFloppyAccess else soundHDDAccess sound(random.nextInt(sound.length)).play() - }) + } } } } diff --git a/src/main/scala/ocelot/desktop/audio/SoundSource.scala b/src/main/scala/ocelot/desktop/audio/SoundSource.scala index 0acef73..4b70584 100644 --- a/src/main/scala/ocelot/desktop/audio/SoundSource.scala +++ b/src/main/scala/ocelot/desktop/audio/SoundSource.scala @@ -24,14 +24,22 @@ class SoundSource(soundBuffer: SoundBuffer, looping: Boolean = false) extends Re def getSourceId: Int = sourceId - def getStatus: Int = if (sourceId == -1) -1 else AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) + def getStatus: Int = ensureInit(-1)(AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE)) def isPlaying: Boolean = getStatus == AL10.AL_PLAYING def isPaused: Boolean = getStatus == AL10.AL_PAUSED def isStopped: Boolean = getStatus == AL10.AL_STOPPED - def play(): Unit = if (!isPlaying) AL10.alSourcePlay(sourceId) - def pause(): Unit = if (isPlaying) AL10.alSourcePause(sourceId) - def stop(): Unit = if (isPlaying || isPaused) AL10.alSourceStop(sourceId) + def play(): Unit = ensureInit() { + if (!isPlaying) AL10.alSourcePlay(sourceId) + } + + def pause(): Unit = ensureInit() { + if (isPlaying) AL10.alSourcePause(sourceId) + } + + def stop(): Unit = ensureInit() { + if (isPlaying || isPaused) AL10.alSourceStop(sourceId) + } def setVolume(value: Float): Unit = { if (sourceId != -1) { @@ -45,4 +53,10 @@ class SoundSource(soundBuffer: SoundBuffer, looping: Boolean = false) extends Re AL10.alDeleteSources(sourceId) } } + + /** + * Runs `default` if `sourceId == -1`; otherwise, returned the value provided by `ifInit`. + */ + private def ensureInit[T](default: => T)(ifInit: => T): T = if (sourceId == -1) default else ifInit + private def ensureInit()(ifInit: => Unit): Unit = ensureInit(())(ifInit) } diff --git a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala index 9467210..424ddf8 100644 --- a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala @@ -93,4 +93,10 @@ case class Rect2D(x: Float, y: Float, w: Float, h: Float) { def mapY(f: Float => Float): Rect2D = copy(y = f(y)) def mapW(f: Float => Float): Rect2D = copy(w = f(w)) def mapH(f: Float => Float): Rect2D = copy(h = f(h)) + + def mapTopLeft(f: Vector2D => Vector2D): Rect2D = { + val topLeft = f(Vector2D(x, y)) + + copy(x = topLeft.x, y = topLeft.y) + } } diff --git a/src/main/scala/ocelot/desktop/node/NodeRegistry.scala b/src/main/scala/ocelot/desktop/node/NodeRegistry.scala index adf3ae1..a0c0b5e 100644 --- a/src/main/scala/ocelot/desktop/node/NodeRegistry.scala +++ b/src/main/scala/ocelot/desktop/node/NodeRegistry.scala @@ -1,7 +1,6 @@ package ocelot.desktop.node import ocelot.desktop.node.nodes._ -import ocelot.desktop.util.TrayInventory import totoro.ocelot.brain.entity.{Cable, Case, FloppyDiskDrive, Relay, Screen} import scala.collection.mutable @@ -38,7 +37,5 @@ object NodeRegistry { })) // TODO: use a proper icon - register(NodeType("Tray", "nodes/NewNode", -1, () => { - new TrayNode(new TrayInventory) - })) + register(NodeType("Tray", "nodes/NewNode", -1, () => new TrayNode)) } diff --git a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala index 607d03f..6770ab8 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala @@ -4,22 +4,24 @@ import ocelot.desktop.OcelotDesktop import ocelot.desktop.audio.{Audio, SoundSource} import ocelot.desktop.color.Color import ocelot.desktop.graphics.Graphics -import ocelot.desktop.node.{EnvironmentNode, Node} +import ocelot.desktop.node.EnvironmentNode import ocelot.desktop.ui.event.sources.KeyEvents import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} +import ocelot.desktop.ui.widget.WorkspaceView import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu} -import ocelot.desktop.ui.widget.slot._ +import ocelot.desktop.ui.widget.inventory.item._ +import ocelot.desktop.ui.widget.inventory.slot._ +import ocelot.desktop.ui.widget.inventory.{InventorySlotGroup, InventorySlotWidget} import ocelot.desktop.util.{ResourceManager, TierColor} import org.lwjgl.input.Keyboard -import totoro.ocelot.brain.entity.traits.{Computer, Entity, Floppy, GenericCPU, Inventory} -import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, HDDUnmanaged, Memory} -import totoro.ocelot.brain.loot.Loot -import totoro.ocelot.brain.nbt.NBTTagCompound -import totoro.ocelot.brain.util.Tier +import totoro.ocelot.brain.entity.Case +import totoro.ocelot.brain.entity.traits.{Computer, Entity} +import totoro.ocelot.brain.nbt.{NBT, NBTTagCompound} +import totoro.ocelot.brain.util.{Persistable, Tier} import scala.reflect.ClassTag -class ComputerNode(val computer: Case, setup: Boolean = true) extends EnvironmentNode { +class ComputerNode(val computer: Case, fillDefault: Boolean = true) extends EnvironmentNode { var lastFilesystemAccess = 0L var eepromSlot: EEPROMSlot = _ @@ -29,39 +31,51 @@ class ComputerNode(val computer: Case, setup: Boolean = true) extends Environmen var diskSlots: Array[DiskSlot] = _ var floppySlot: Option[FloppySlot] = None + val slotGroup = new InventorySlotGroup + val soundComputerRunning = new SoundSource(Audio.ComputerRunning, true) setupSlots() - if (setup) { - cpuSlot.owner.put(new CPU(computer.tier.min(2))) - memorySlots(0).owner.put(new Memory(computer.tier.min(2) * 2 + 1)) - memorySlots(1).owner.put(new Memory(computer.tier.min(2) * 2 + 1)) - cardSlots(0).owner.put(new GraphicsCard(computer.tier.min(1))) - floppySlot.map(_.owner).foreach(_.put(Loot.OpenOsFloppy.create())) - eepromSlot.owner.put(Loot.OpenOsEEPROM.create()) -// refitSlots() + if (fillDefault) { + slotGroup.pushItems(Iterator( + new CPUItemPrototype.CPU(computer.tier.min(Tier.Three)).build, + new MemoryItemPrototype(computer.tier.min(Tier.Three) * 2 + 1).build, + new MemoryItemPrototype(computer.tier.min(Tier.Three) * 2 + 1).build, + new GraphicsCardItemPrototype(computer.tier.min(Tier.Two)).build, + new FloppyItemPrototype.Managed("openos").build, + new EEPROMItemPrototype(Some("lua-bios")).build, + )) + // TODO: investigate other uses of `add` to make sure they don't add something twice OcelotDesktop.workspace.add(computer) } - refitSlots() - def this(nbt: NBTTagCompound) { this({ val address = nbt.getString("address") OcelotDesktop.workspace.entityByAddress(address).get.asInstanceOf[Case] - }, setup = false) + }, fillDefault = false) + + load(nbt) + } + + override def load(nbt: NBTTagCompound): Unit = { super.load(nbt) + + slotGroup.load(nbt.getTagList(ComputerNode.InventoryTag, NBT.TAG_COMPOUND)) } override def save(nbt: NBTTagCompound): Unit = { super.save(nbt) + nbt.setString("address", computer.node.address) + nbt.setTag(ComputerNode.InventoryTag, slotGroup.save()) } override def environment: Computer = computer override val icon: String = "nodes/Computer" + override def iconColor: Color = TierColor.get(computer.tier) override protected val canOpen = true @@ -114,79 +128,17 @@ class ComputerNode(val computer: Case, setup: Boolean = true) extends Environmen } private def changeTier(n: Int): Unit = { + val items = slotGroup.iterator.map(_._2).flatMap(_.item).toSeq computer.tier = n setupSlots() - refitSlots() + slotGroup.pushItems(items.iterator) + if (currentWindow != null) currentWindow.updateSlots() } - private def slotAccepts(slot: Inventory#Slot, entity: Entity): Boolean = entity match { - case cpu: GenericCPU => cpuSlot.owner.index == slot.index && cpuSlot.tier >= cpu.cpuTier - case mem: Memory => memorySlots - .exists(memSlot => memSlot.owner.index == slot.index && memSlot.tier >= (mem.tier + 1) / 2 - 1) - case hdd: HDDManaged => diskSlots - .exists(diskSlot => diskSlot.owner.index == slot.index && diskSlot.tier >= hdd.tier) - case hdd: HDDUnmanaged => diskSlots - .exists(diskSlot => diskSlot.owner.index == slot.index && diskSlot.tier >= hdd.tier) - case _: EEPROM => eepromSlot.owner.index == slot.index - case _: Floppy => floppySlot.exists(_.owner.index == slot.index) - case card: Entity => cardSlots - .exists(cardSlot => cardSlot.owner.index == slot.index && cardSlot.tier >= CardRegistry.getTier(card)) - } - - private def isSlotValid(slot: Inventory#Slot): Boolean = slot.get.exists(slotAccepts(slot, _)) - - private def reloadSlots(): Unit = { - cpuSlot.reloadItem() - memorySlots.foreach(_.reloadItem()) - cardSlots.foreach(_.reloadItem()) - diskSlots.foreach(_.reloadItem()) - eepromSlot.reloadItem() - floppySlot.foreach(_.reloadItem()) - } - - private def refitSlots(): Unit = { - if (computer.inventory.forall(isSlotValid)) { - reloadSlots() - - return - } - - val entities = computer.inventory.entities.toArray - computer.inventory.clear() - - cpuSlot._item = None - for (slot <- memorySlots) slot._item = None - for (slot <- cardSlots) slot._item = None - for (slot <- diskSlots) slot._item = None - eepromSlot._item = None - floppySlot.foreach(_._item = None) - - def findBestSlot[T <: InventorySlot[_]](entity: Entity, candidates: Array[T], tierProvider: T => Option[Int]): Option[T] = { - candidates.iterator - .filter(_.owner.isEmpty) - .filter(slot => slotAccepts(slot.owner, entity)) - .minByOption(tierProvider(_).getOrElse(Int.MinValue)) - } - - for (entity <- entities) { - val newSlot = entity match { - case _: GenericCPU => findBestSlot[CPUSlot](entity, Array(cpuSlot), slot => Some(slot.tier)) - case _: Memory => findBestSlot[MemorySlot](entity, memorySlots, slot => Some(slot.tier)) - case _: HDDManaged => findBestSlot[DiskSlot](entity, diskSlots, slot => Some(slot.tier)) - case _: HDDUnmanaged => findBestSlot[DiskSlot](entity, diskSlots, slot => Some(slot.tier)) - case _: EEPROM => findBestSlot[EEPROMSlot](entity, Array(eepromSlot), _ => None) - case _: Floppy => findBestSlot[FloppySlot](entity, floppySlot.toArray, _ => None) - case _: Entity => findBestSlot[CardSlot](entity, cardSlots, slot => Some(slot.tier)) - } - - newSlot.foreach(_.owner.put(entity)) - } - - reloadSlots() - } - private def setupSlots(): Unit = { + slotGroup.clear() + var slotIndex = 0 def nextSlot(): computer.Slot = { @@ -195,14 +147,15 @@ class ComputerNode(val computer: Case, setup: Boolean = true) extends Environmen result } - def addSlot[T <: InventorySlot[_]](factory: computer.Slot => T): T = { + def addSlot[T <: InventorySlotWidget[_ <: Entity with Persistable]](factory: (computer.Slot, WorkspaceView) => T): T = { val slot = nextSlot() - val widget = factory(slot) + val widget = factory(slot, workspaceView) + slotGroup.addSlot(widget) widget } - def addSlots[T <: InventorySlot[_] : ClassTag](factories: (computer.Slot => T)*): Array[T] = { + def addSlots[T <: InventorySlotWidget[_ <: Entity with Persistable] : ClassTag](factories: ((computer.Slot, WorkspaceView) => T)*): Array[T] = { val array = Array.newBuilder[T] for (factory <- factories) { @@ -214,41 +167,41 @@ class ComputerNode(val computer: Case, setup: Boolean = true) extends Environmen computer.tier match { case Tier.One => - cardSlots = addSlots(new CardSlot(_, Tier.One), new CardSlot(_, Tier.One)) - memorySlots = addSlots(new MemorySlot(_, Tier.One)) - diskSlots = addSlots(new DiskSlot(_, Tier.One)) + cardSlots = addSlots(new CardSlot(_, Tier.One, _), new CardSlot(_, Tier.One, _)) + memorySlots = addSlots(new MemorySlot(_, Tier.One, _)) + diskSlots = addSlots(new DiskSlot(_, Tier.One, _)) floppySlot = None - cpuSlot = addSlot(new CPUSlot(_, this, Tier.One)) + cpuSlot = addSlot(new CPUSlot(_, this, Tier.One, _)) // no idea why on earth the memory slots are split in two here - memorySlots :+= addSlot(new MemorySlot(_, Tier.One)) - eepromSlot = addSlot(new EEPROMSlot(_)) + memorySlots :+= addSlot(new MemorySlot(_, Tier.One, _)) + eepromSlot = addSlot(new EEPROMSlot(_, _)) case Tier.Two => - cardSlots = addSlots(new CardSlot(_, Tier.Two), new CardSlot(_, Tier.One)) - memorySlots = addSlots(new MemorySlot(_, Tier.Two), new MemorySlot(_, Tier.Two)) - diskSlots = addSlots(new DiskSlot(_, Tier.Two), new DiskSlot(_, Tier.One)) + cardSlots = addSlots(new CardSlot(_, Tier.Two, _), new CardSlot(_, Tier.One, _)) + memorySlots = addSlots(new MemorySlot(_, Tier.Two, _), new MemorySlot(_, Tier.Two, _)) + diskSlots = addSlots(new DiskSlot(_, Tier.Two, _), new DiskSlot(_, Tier.One, _)) floppySlot = None - cpuSlot = addSlot(new CPUSlot(_, this, Tier.Two)) - eepromSlot = addSlot(new EEPROMSlot(_)) + cpuSlot = addSlot(new CPUSlot(_, this, Tier.Two, _)) + eepromSlot = addSlot(new EEPROMSlot(_, _)) case _ => cardSlots = if (computer.tier == Tier.Three) { - addSlots(new CardSlot(_, Tier.Three), new CardSlot(_, Tier.Two), new CardSlot(_, Tier.Two)) + addSlots(new CardSlot(_, Tier.Three, _), new CardSlot(_, Tier.Two, _), new CardSlot(_, Tier.Two, _)) } else { - addSlots(new CardSlot(_, Tier.Three), new CardSlot(_, Tier.Three), new CardSlot(_, Tier.Three)) + addSlots(new CardSlot(_, Tier.Three, _), new CardSlot(_, Tier.Three, _), new CardSlot(_, Tier.Three, _)) } - memorySlots = addSlots(new MemorySlot(_, Tier.Three), new MemorySlot(_, Tier.Three)) + memorySlots = addSlots(new MemorySlot(_, Tier.Three, _), new MemorySlot(_, Tier.Three, _)) diskSlots = if (computer.tier == Tier.Three) { - addSlots(new DiskSlot(_, Tier.Three), new DiskSlot(_, Tier.Two)) + addSlots(new DiskSlot(_, Tier.Three, _), new DiskSlot(_, Tier.Two, _)) } else { - addSlots(new DiskSlot(_, Tier.Three), new DiskSlot(_, Tier.Three)) + addSlots(new DiskSlot(_, Tier.Three, _), new DiskSlot(_, Tier.Three, _)) } - floppySlot = Some(addSlot(new FloppySlot(_))) - cpuSlot = addSlot(new CPUSlot(_, this, Tier.Three)) - eepromSlot = addSlot(new EEPROMSlot(_)) + floppySlot = Some(addSlot(new FloppySlot(_, _))) + cpuSlot = addSlot(new CPUSlot(_, this, Tier.Three, _)) + eepromSlot = addSlot(new EEPROMSlot(_, _)) } } @@ -289,3 +242,7 @@ class ComputerNode(val computer: Case, setup: Boolean = true) extends Environmen super.dispose() } } + +object ComputerNode { + private final val InventoryTag = "inventory" +} diff --git a/src/main/scala/ocelot/desktop/node/nodes/DiskDriveNode.scala b/src/main/scala/ocelot/desktop/node/nodes/DiskDriveNode.scala index 8d468a0..a316075 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/DiskDriveNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/DiskDriveNode.scala @@ -4,32 +4,43 @@ import ocelot.desktop.OcelotDesktop import ocelot.desktop.color.IntColor import ocelot.desktop.graphics.Graphics import ocelot.desktop.node.EnvironmentNode -import ocelot.desktop.ui.widget.slot.FloppySlot +import ocelot.desktop.ui.widget.inventory.{InventorySlotGroup, Item} +import ocelot.desktop.ui.widget.inventory.item.FloppyItemPrototype +import ocelot.desktop.ui.widget.inventory.slot.FloppySlot import totoro.ocelot.brain.entity.FloppyDiskDrive import totoro.ocelot.brain.entity.traits.{Environment, Floppy} -import totoro.ocelot.brain.loot.Loot -import totoro.ocelot.brain.nbt.NBTTagCompound -import totoro.ocelot.brain.util.DyeColor +import totoro.ocelot.brain.nbt.{NBT, NBTTagCompound} +import totoro.ocelot.brain.util.{DyeColor, Persistable} class DiskDriveNode(val diskDrive: FloppyDiskDrive) extends EnvironmentNode { var lastAccess = 0L OcelotDesktop.workspace.add(diskDrive) - val slot: FloppySlot = new FloppySlot(diskDrive.inventory(0)) - slot.item = floppy.getOrElse(Loot.OpenOsFloppy.create()) + val slot = new FloppySlot(diskDrive.inventory(0), workspaceView) + val slotGroup = new InventorySlotGroup(slot) + slot.item = slot.item.orElse(Some(new FloppyItemPrototype.Managed("openos").build)) def this(nbt: NBTTagCompound) { this({ val address = nbt.getString("address") OcelotDesktop.workspace.entityByAddress(address).get.asInstanceOf[FloppyDiskDrive] }) + + load(nbt) + } + + override def load(nbt: NBTTagCompound): Unit = { super.load(nbt) + + slotGroup.load(nbt.getTagList(DiskDriveNode.InventoryTag, NBT.TAG_COMPOUND)) } override def save(nbt: NBTTagCompound): Unit = { super.save(nbt) + nbt.setString("address", diskDrive.node.address) + nbt.setTag(DiskDriveNode.InventoryTag, slotGroup.save()) } override def environment: Environment = diskDrive @@ -63,16 +74,14 @@ class DiskDriveNode(val diskDrive: FloppyDiskDrive) extends EnvironmentNode { if (System.currentTimeMillis() - diskDrive.lastDiskAccess < 400 && Math.random() > 0.1) g.sprite("nodes/DiskDriveActivity", position.x + 2, position.y + 2, size.width - 4, size.height - 4) - if (slot.item.isDefined) - g.sprite("nodes/DiskDriveFloppy", position.x + 2, position.y + 2, size.width - 4, size.height - 4, IntColor(colorMap(slot.item.get.color))) + slot.item.foreach(item => + g.sprite("nodes/DiskDriveFloppy", position.x + 2, position.y + 2, size.width - 4, size.height - 4, IntColor(colorMap(item.element.color))) + ) } override val window: Option[DiskDriveWindow] = Some(new DiskDriveWindow(this)) +} - private def floppy: Option[Floppy] = { - diskDrive.inventory(0).get match { - case Some(floppy: Floppy) => Some(floppy) - case _ => None - } - } +object DiskDriveNode { + private final val InventoryTag = "inventory" } \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/node/nodes/TrayNode.scala b/src/main/scala/ocelot/desktop/node/nodes/TrayNode.scala index 219c6c7..9d250bf 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/TrayNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/TrayNode.scala @@ -1,33 +1,34 @@ package ocelot.desktop.node.nodes -import ocelot.desktop.OcelotDesktop import ocelot.desktop.node.Node -import ocelot.desktop.ui.widget.slot.EntitySlot -import ocelot.desktop.util.TrayInventory -import totoro.ocelot.brain.nbt.NBTTagCompound -import totoro.ocelot.brain.nbt.persistence.NBTPersistence +import ocelot.desktop.ui.widget.inventory.SlotGroup +import ocelot.desktop.ui.widget.inventory.slot.AnySlot +import ocelot.desktop.util.ExtendedTuple2 +import totoro.ocelot.brain.nbt.{NBT, NBTTagCompound} -class TrayNode(trayInventory: TrayInventory) extends Node { - val slots: Array[EntitySlot] = (0 until TrayNode.InventorySize) - .iterator - .map(trayInventory.inventory.apply) - .map(new EntitySlot(_)) - .toArray +class TrayNode extends Node { + val slotGroup = new SlotGroup() + + slotGroup ++= Iterator.continually(new AnySlot(workspaceView)) + .zipWithIndex + .map(_.flipped) + .take(TrayNode.InventorySize) def this(nbt: NBTTagCompound) { - this({ - val inventoryNbt = nbt.getCompoundTag(TrayNode.InventoryTag) - NBTPersistence.load(inventoryNbt, OcelotDesktop.workspace).asInstanceOf[TrayInventory] - }) + this() + load(nbt) + } + override def load(nbt: NBTTagCompound): Unit = { super.load(nbt) + + slotGroup.load(nbt.getTagList(TrayNode.InventoryTag, NBT.TAG_COMPOUND)) } override def save(nbt: NBTTagCompound): Unit = { super.save(nbt) - val inventoryNbt = NBTPersistence.save(trayInventory) - nbt.setTag(TrayNode.InventoryTag, inventoryNbt) + nbt.setTag(TrayNode.InventoryTag, slotGroup.save()) } // TODO @@ -43,9 +44,9 @@ class TrayNode(trayInventory: TrayInventory) extends Node { } object TrayNode { - private val InventoryTag = "inventory" + private final val InventoryTag = "inventory" - val InventoryWidth: Int = 9 - val InventoryHeight: Int = 8 - val InventorySize: Int = InventoryWidth * InventoryHeight + final val InventoryWidth: Int = 9 + final val InventoryHeight: Int = 8 + final val InventorySize: Int = InventoryWidth * InventoryHeight } \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/node/nodes/TrayWindow.scala b/src/main/scala/ocelot/desktop/node/nodes/TrayWindow.scala index e6665ca..23e1b0a 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/TrayWindow.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/TrayWindow.scala @@ -17,7 +17,7 @@ class TrayWindow(owner: TrayNode) extends BasicWindow { for (column <- 0 until TrayNode.InventoryHeight) { val index = row * TrayNode.InventoryWidth + column - children :+= owner.slots(index) + children :+= owner.slotGroup(index) } } } diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index 45ffe6a..3a67297 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -208,7 +208,7 @@ object UiHandler extends Logging { if (!audioDisabled) Audio.update() - OcelotDesktop.withTickLockAcquired(() => { + OcelotDesktop.withTickLockAcquired { updateWindowSizeAndPosition() KeyEvents.update() MouseEvents.update() @@ -220,7 +220,7 @@ object UiHandler extends Logging { Profiler.startTimeMeasurement("001_draw") draw() Profiler.endTimeMeasurement("001_draw") - }) + } Profiler.startTimeMeasurement("002_sleep") ticker.waitNext() diff --git a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala index 9e0412a..a817957 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala @@ -46,14 +46,14 @@ class RootWidget(setupDefaultWorkspace: Boolean = true) extends Widget { case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_F1, _) => isDebugViewVisible = !isDebugViewVisible case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_F3, _) => - OcelotDesktop.withTickLockAcquired(() => { + OcelotDesktop.withTickLockAcquired { val backendNBT = new NBTTagCompound val frontendNBT = new NBTTagCompound OcelotDesktop.workspace.save(backendNBT) UiHandler.root.workspaceView.save(frontendNBT) OcelotDesktop.workspace.load(backendNBT) UiHandler.root.workspaceView.load(frontendNBT) - }) + } } override def draw(g: Graphics): Unit = { diff --git a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala index 6446430..c69b814 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala @@ -9,11 +9,12 @@ import ocelot.desktop.node.{EnvironmentNode, Node, NodePort} import ocelot.desktop.ui.event._ import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler} import ocelot.desktop.ui.layout.{CopyLayout, Layout} +import ocelot.desktop.ui.widget.inventory.{Item, SlotWidget} import ocelot.desktop.ui.widget.window.{NodeSelector, ProfilerWindow, WindowPool} import ocelot.desktop.util.DrawUtils import ocelot.desktop.util.animation.ValueAnimation import org.lwjgl.input.Keyboard -import totoro.ocelot.brain.entity.traits.{Environment, SidedEnvironment} +import totoro.ocelot.brain.entity.traits.{Entity, Environment, SidedEnvironment} import totoro.ocelot.brain.entity.{Case, Screen} import totoro.ocelot.brain.nbt.ExtendedNBT._ import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound} @@ -32,6 +33,9 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover var cameraOffset: Vector2D = Vector2D(0, 0) var newConnection: Option[(EnvironmentNode, NodePort, Vector2D)] = None + case class DraggedItem(sourceSlot: SlotWidget[_], item: Item[Entity], pos: Vector2D) + var draggedItem: Option[DraggedItem] = None + private var newNodePos = Vector2D(0, 0) private val gridAlpha = new ValueAnimation(0, 1f) private val portsAlpha = new ValueAnimation(0, 7f) @@ -432,6 +436,10 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover g.translate(-position.x, -position.y) } + private def drawDraggedItem(g: Graphics): Unit = draggedItem foreach { case DraggedItem(sourceSlot, item, pos) => + // TODO + } + override def draw(g: Graphics): Unit = { g.setScissor(bounds.x, bounds.y, bounds.w, bounds.h) @@ -502,6 +510,8 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover drawChildren(g) + drawDraggedItem(g) + g.setScissor() } diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/DecoratedSlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/DecoratedSlotWidget.scala new file mode 100644 index 0000000..19f93a6 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/DecoratedSlotWidget.scala @@ -0,0 +1,14 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.graphics.{Graphics, IconDef} + +trait DecoratedSlotWidget[E] extends TieredSlotWidget[E] { + def bgIcon: Option[IconDef] + + def tierIcon: Option[IconDef] = slotTier.map("icons/Tier" + _).map(new IconDef(_)) + + override protected def drawEmptySlot(g: Graphics): Unit = { + bgIcon.foreach(g.sprite(_, iconBounds)) + tierIcon.foreach(g.sprite(_, iconBounds)) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotGroup.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotGroup.scala new file mode 100644 index 0000000..2aad878 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotGroup.scala @@ -0,0 +1,154 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.util.Logging +import totoro.ocelot.brain.entity.traits.{Entity, Inventory} +import totoro.ocelot.brain.event.{EventBus, InventoryEntityAddedEvent, InventoryEntityRemovedEvent} +import totoro.ocelot.brain.nbt.NBTTagList +import totoro.ocelot.brain.util.Persistable + +import scala.collection.mutable + +class InventorySlotGroup extends SlotGroup { + def this(slots: InventorySlotWidget[_ <: Entity with Persistable]*) { + this() + + slots.foreach(addSlot) + } + + private val trackedInventories = mutable.Map.empty[Inventory, TrackedInventoryInfo] + private val trackedSlots = mutable.Map.empty[Inventory#Slot, InventorySlotWidget[_ <: Entity with Persistable]] + + override def addSlot(index: Int, slot: SlotWidget[_]): Unit = slot match { + case slot: InventorySlotWidget[_] => + if (index != slot.slave.index) { + throw new IllegalArgumentException(s"tried to add an InventorySlot (index: ${slot.slave.index}) " + + s"to a slot it doesn't belong to (index: $index)") + } + + addSlot(slot) + + case _ => super.addSlot(index, slot) + } + + def addSlot(slot: InventorySlotWidget[_ <: Entity with Persistable]): Unit = { + val slave = slot.slave + val index = slave.index + + // we have to explicitly removeSlot here in case we've had event listeners set up for the slot + removeSlot(index) + addEventListener(slave) + trackedSlots += slot.slave -> slot + super.addSlot(index, slot) + } + + override def removeSlot(index: Int): Option[SlotWidget[_]] = + super.removeSlot(index).tapEach(unregisterSlot).headOption + + private def unregisterSlot(slot: SlotWidget[_]): Unit = slot match { + case slot: InventorySlotWidget[_] => + removeEventListener(slot.slave) + trackedSlots.remove(slot.slave) + + case _ => + } + + override def clear(): Unit = { + _slots.valuesIterator.foreach(unregisterSlot) + + _slots.clear() + } + + private def addEventListener(slot: Inventory#Slot): Unit = { + val owner = slot.inventory.owner + + trackedInventories.updateWith(owner) { + case Some(info) => Some(info.withRefIncremented) + + case None => + Some(TrackedInventoryInfo(EventBus.subscribe(onEntityAdded), EventBus.subscribe(onEntityRemoved))) + } + } + + private def removeEventListener(slot: Inventory#Slot): Unit = { + val owner = slot.inventory.owner + + trackedInventories.updateWith(owner) { + case Some(info@TrackedInventoryInfo(_, _, refCount)) if refCount > 0 => Some(info.withRefDecremented) + + case Some(TrackedInventoryInfo(onAddedSubscription, onRemovedSubscription, _)) => + onAddedSubscription.cancel() + onRemovedSubscription.cancel() + + None + + case None => None + } + } + + private def onEntityAdded(event: InventoryEntityAddedEvent): Unit = + trackedSlots.get(event.slot).filterNot(_.transient).foreach(reloadSlot) + + private def onEntityRemoved(event: InventoryEntityRemovedEvent): Unit = + trackedSlots.get(event.slot).filterNot(_.transient).foreach(reloadSlot) + + private def reloadSlot(slot: InventorySlotWidget[_ <: Entity]): Unit = { + val slave = slot.slave + + (slave.get, slot.item.map(_.element)) match { + case (Some(slaveEntity), Some(masterEntity)) if slaveEntity.eq(masterEntity) => // in sync + case (None, None) => // in sync + + case (a, b) => // de-sync + logger.atDebug().withThrowable(new Exception("A de-sync has occured")) + .log(s"Have $a in slave and $b in master: de-sync!") + slot.set(InventorySlotGroup.recoverItemFromSlot(slave)) + } + } + + override def load(nbt: NBTTagList): Unit = { + val nbtLoadedItems = loadItemsFromNbt(nbt) + val recoveredItems = recoverItemsFromInventory(nbtLoadedItems) + + setItems(nbtLoadedItems ++ recoveredItems) + } + + override def load(): Unit = setItems(recoverItemsFromInventory(Map.empty)) + + private def recoverItemsFromInventory(loadedItems: Map[Int, Item[Entity]]): Map[Int, Item[Entity]] = + _slots.iterator + .filter { case (index, _) => _slots.contains(index) } + .filterNot { case (index, _) => loadedItems.contains(index) } + // fall back to trying to recover items from inventory contents + .collect { + case (_, slot: InventorySlotWidget[_]) => slot.slave + } + .flatMap(slave => InventorySlotGroup.recoverItemFromSlot(slave).map((slave.index, _))) + .toMap + + private case class TrackedInventoryInfo(onAddedSubscription: EventBus.Subscription, + onRemovedSubscription: EventBus.Subscription, + refCount: Int = 1) { + def withRefIncremented: TrackedInventoryInfo = + TrackedInventoryInfo(onAddedSubscription, onRemovedSubscription, refCount + 1) + + def withRefDecremented: TrackedInventoryInfo = + TrackedInventoryInfo(onAddedSubscription, onRemovedSubscription, (refCount - 1) max 0) + } +} + +object InventorySlotGroup extends Logging { + def recoverItemFromSlot(slot: Inventory#Slot): Option[Item[Entity]] = { + slot.get.flatMap(entity => { + val item = ItemRegistry.recover(entity) + + if (item.isEmpty) { + logger.atWarn().withThrowable( + new Exception(s"Inventory ${slot.inventory.owner} stores an Entity $entity (slot index: ${slot.index}) " + + s"that cannot be recovered to an Item") + ).log("Could not recover an item from an Inventory") + } + + item + }) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotWidget.scala new file mode 100644 index 0000000..59e6d37 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotWidget.scala @@ -0,0 +1,31 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.ui.UiHandler +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} +import totoro.ocelot.brain.entity.traits.{Entity, Environment, Inventory} +import totoro.ocelot.brain.util.Persistable + +import scala.reflect.ClassTag + +abstract class InventorySlotWidget[E <: Entity with Persistable : ClassTag](val slave: Inventory#Slot, + workspaceView: WorkspaceView) + extends SlotWidget[E](workspaceView) { + + override def onAdded(item: Item[Element]): Unit = slave.put(item.element) + + override def onRemoved(item: Item[Element]): Unit = slave.remove() + + override def fillRmbMenu(menu: ContextMenu): Unit = { + item match { + case Some(env: Environment) => + // TODO: move to EnvironmentItemPrototype.Instance + menu.addEntry(new ContextMenuEntry("Copy address", () => { + UiHandler.clipboard = env.node.address + })) + + case _ => + } + super.fillRmbMenu(menu) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/Item.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/Item.scala new file mode 100644 index 0000000..f545b88 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/Item.scala @@ -0,0 +1,17 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.contextmenu.ContextMenu + +/** + * Something that can be put into a [[SlotWidget]]. + */ +trait Item[+E] { + def prototype: ItemPrototype + + def icon: IconDef + + def fillRmbMenu(menu: ContextMenu): Unit = {} + + def element: E +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemPrototype.scala new file mode 100644 index 0000000..60bd245 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemPrototype.scala @@ -0,0 +1,33 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory + +import scala.reflect.ClassTag + +trait ItemPrototype { + type Element + type Item <: inventory.Item[Element] + + val elementTag: ClassTag[Element] + + def build: Item + + def name: String + + def icon: IconDef +} + +object ItemPrototype { + trait WithInstance extends ItemPrototype { + proto => + + override type Item <: Instance + + abstract class Instance(var element: Element) extends inventory.Item[Element] { + override final def prototype: ItemPrototype = proto + + override final def icon: IconDef = proto.icon + } + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRecoverer.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRecoverer.scala new file mode 100644 index 0000000..df9a080 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRecoverer.scala @@ -0,0 +1,14 @@ +package ocelot.desktop.ui.widget.inventory + +import scala.reflect.ClassTag + +/** + * Recovers an [[Item]] by its element. + */ +trait ItemRecoverer { + type Element + + implicit val elementTag: ClassTag[Element] + + def recover(element: Element): Item[Element] +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRegistry.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRegistry.scala new file mode 100644 index 0000000..480eb30 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRegistry.scala @@ -0,0 +1,60 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.graphics.IconDef + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer + +object ItemRegistry { + private val recovererRegistry = mutable.Map.empty[Class[_], ItemRecoverer] + private val nodes = ArrayBuffer.empty[Node] + + def register(prototype: ItemPrototype): ItemPrototype = { + nodes += Leaf(prototype) + + prototype + } + + def register(group: Group): Group = { + nodes += group + + group + } + + def register(recoverer: ItemRecoverer): Unit = + recovererRegistry(recoverer.elementTag.runtimeClass) = recoverer + + def recover[E](element: E): Option[Item[E]] = { + recovererRegistry + .get(element.getClass) + .map(recoverer => { + // wild type-casting ¯\_(ツ)_/¯ + // (should be safe though) + recoverer.recover(element.asInstanceOf[recoverer.Element]).asInstanceOf[Item[E]] + }) + } + + def children: Iterator[Node] = nodes.iterator + + sealed trait Node + + abstract case class Group(name: String, icon: Option[IconDef]) extends Node { self => + private val _children: ArrayBuffer[Node] = ArrayBuffer.empty + + final def children: Iterator[Node] = _children.iterator + + def register(prototype: ItemPrototype): ItemPrototype = { + _children += Leaf(prototype) + + prototype + } + + def register(group: Group): Group = { + _children += group + + group + } + } + + case class Leaf(prototype: ItemPrototype) extends Node +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/LmbItemSelectionSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/LmbItemSelectionSlot.scala new file mode 100644 index 0000000..2f4fcf5 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/LmbItemSelectionSlot.scala @@ -0,0 +1,30 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu} +import ocelot.desktop.ui.widget.inventory.ItemRegistry.{Group, Node} + +trait LmbItemSelectionSlot[E] extends SlotWidget[E] { + override def fillLmbMenu(menu: ContextMenu): Unit = { + def traverse(node: Node): Option[ContextMenuEntry] = node match { + case group@Group(name, icon) => + val entries = group.children.flatMap(traverse).toSeq + + Option.when(entries.nonEmpty) { + new ContextMenuSubmenu(name, icon) { + entries.foreach(addEntry) + } + } + + case ItemRegistry.Leaf(prototype) => + Option.when(accepts(prototype)) { + new ContextMenuEntry(prototype.name, () => set(prototype.build), Some(prototype.icon)) + } + } + + ItemRegistry.children.flatMap(traverse).foreach(menu.addEntry) + + super.fillLmbMenu(menu) + } + + override def lmbMenuEnabled: Boolean = true +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotGroup.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotGroup.scala new file mode 100644 index 0000000..64870e0 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotGroup.scala @@ -0,0 +1,133 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.OcelotDesktop +import ocelot.desktop.util.Logging +import totoro.ocelot.brain.entity.traits.Entity +import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList +import totoro.ocelot.brain.nbt.persistence.NBTPersistence +import totoro.ocelot.brain.nbt.{NBTTagCompound, NBTTagList} +import totoro.ocelot.brain.util.Persistable + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer + +class SlotGroup extends mutable.Growable[(Int, SlotWidget[_])] with Logging with PartialFunction[Int, SlotWidget[_]] { + private final val SlotTag = "slot" + private final val ItemTag = "item" + + protected val _slots = mutable.Map.empty[Int, SlotWidget[_]] + + def iterator: Iterator[(Int, SlotWidget[_])] = _slots.iterator + + override def apply(index: Int): SlotWidget[_] = _slots(index) + + override def isDefinedAt(index: Int): Boolean = _slots.isDefinedAt(index) + + def addSlot(index: Int, slot: SlotWidget[_]): Unit = { + _slots += index -> slot + } + + def removeSlot(index: Int): Option[SlotWidget[_]] = _slots.remove(index) + + override def clear(): Unit = _slots.clear() + + final override def addOne(elem: (Int, SlotWidget[_])): SlotGroup.this.type = { + (addSlot _).tupled(elem) + + this + } + + /** + * Pushes an item into the first (in index order) empty slot that accepts it. + * + * @return the index of the slot the item was inserted into, or `None` if no slot was found + */ + def pushItem[E](item: Item[E]): Option[Int] = { + val (index, slot) = _slots + .iterator + .filter(_._2.isEmpty) + .filter(_._2.accepts(item)) + .maxByOption(_._1) + .unzip + slot.foreach(_.set(item)) + + index + } + + /** + * Pushes items from an iterator consecutively as if by calling [[pushItem]] on each item + * + * @return the number of items inserted + */ + def pushItems(items: Iterator[Item[_]]): Int = { + val queue = ArrayBuffer.empty[(Int, SlotWidget[_])] + queue ++= _slots.iterator.filter(_._2.isEmpty) + queue.sortInPlaceBy(_._1) + + var insertedCount = 0 + + for (item <- items) { + val entryIndex = queue.iterator.indexWhere(_._2.accepts(item)) + + if (entryIndex >= 0) { + val (_, slot) = queue(entryIndex) + slot.set(item) + insertedCount += 1 + } + } + + insertedCount + } + + def load(nbt: NBTTagList): Unit = { + setItems(loadItemsFromNbt(nbt)) + } + + def load(): Unit = setItems(Map.empty) + + def save(): NBTTagList = { + val nbt = new NBTTagList + val values = _slots.iterator + .flatMap(slot => slot._2.item.map((slot._1, _))) + .flatMap { + case (index, item: Persistable) => + val slotNbt = new NBTTagCompound + slotNbt.setInteger(SlotTag, index) + slotNbt.setTag(ItemTag, NBTPersistence.save(item)) + + Some(slotNbt) + + case (_, item) => + logger.atError().withLocation().log(s"Item $item is not Persistable and could not be saved.") + + None + } + + nbt.append(values) + + nbt + } + + protected def loadItemsFromNbt(nbt: NBTTagList): Map[Int, Item[Entity]] = + nbt.iterator[NBTTagCompound].flatMap(slotNbt => { + val index = slotNbt.getInteger(SlotTag) + + if (_slots.contains(index)) { + val item = NBTPersistence + .load(slotNbt.getCompoundTag(ItemTag), OcelotDesktop.workspace) + .asInstanceOf[Item[Entity]] + + Some((index, item)) + } else { + logger.atError().withLocation().log(s"Slot $index is non-empty in NBT; however, no SlotWidget manages it") + + None + } + }).toMap + + protected def setItems(items: Map[Int, Item[Entity]]): Unit = { + for ((index, slot) <- _slots) { + slot.set(items.get(index)) + } + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotWidget.scala new file mode 100644 index 0000000..44aadd9 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/SlotWidget.scala @@ -0,0 +1,118 @@ +package ocelot.desktop.ui.widget.inventory + +import ocelot.desktop.geometry.{Rect2D, Size2D} +import ocelot.desktop.graphics.Graphics +import ocelot.desktop.ui.event.handlers.ClickHandler +import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} +import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} +import ocelot.desktop.ui.widget.{Widget, WorkspaceView, inventory} + +import scala.annotation.unused +import scala.reflect.{ClassTag, classTag} + +abstract class SlotWidget[E] protected(val workspaceView: WorkspaceView) + (implicit val elementTag: ClassTag[E]) + extends Widget + with ClickHandler { + + type Element = E + + def transient: Boolean = _transient + + // true iff the slot's item is being changed + protected var _transient: Boolean = false + + def onRemoved(@unused item: Item[Element]): Unit = {} + + def onAdded(@unused item: Item[Element]): Unit = {} + + def fillLmbMenu(menu: ContextMenu): Unit = {} + + def fillRmbMenu(menu: ContextMenu): Unit = { + item.foreach(_.fillRmbMenu(menu)) + + menu.addEntry(new ContextMenuEntry("Remove", () => item = None)) + } + + def accepts(prototype: ItemPrototype): Boolean = + prototype.elementTag.runtimeClass.isAssignableFrom(elementTag.runtimeClass) + + final def accepts(item: inventory.Item[_]): Boolean = accepts(item.prototype) + + def lmbMenuEnabled: Boolean = false + + def rmbMenuEnabled: Boolean = _item.isDefined + + final var _item: Option[Item[Element]] = None + + final def item_=[I <: Item[Element]](v: Option[I]): Unit = { + if (!v.map(_.prototype).forall(accepts)) { + throw new IllegalArgumentException( + s"tried to put an inadmissible item ${v.get} into a SlotWidget requiring ${classTag[Element].runtimeClass}") + } + + _transient = true + _item.foreach(onRemoved) + _item = v + _item.foreach(onAdded) + _transient = false + } + + final def item_=[I <: Item[Element]](v: I): Unit = item = Some(v) + + final def item: Option[Item[Element]] = _item + + final def set(v: Option[inventory.Item[_]]): Unit = v match { + case Some(v: Item[Element@unchecked]) if classTag[Element].runtimeClass.isAssignableFrom(v.element.getClass) => + item = v + + case Some(v) => + throw new IllegalArgumentException( + s"tried to set $v to a SlotWidget requiring ${classTag[Element].runtimeClass}") + + case None => item = None + } + + final def set(v: inventory.Item[_]): Unit = set(Some(v)) + + final def isEmpty: Boolean = _item.isEmpty + + final def nonEmpty: Boolean = _item.nonEmpty + + final override def minimumSize: Size2D = Size2D(36, 36) + + final override def maximumSize: Size2D = minimumSize + + final override def receiveMouseEvents: Boolean = true + + eventHandlers += { + case ClickEvent(MouseEvent.Button.Left, _) if lmbMenuEnabled => + val menu = new ContextMenu + fillLmbMenu(menu) + root.get.contextMenus.open(menu) + + case ClickEvent(MouseEvent.Button.Right, _) if rmbMenuEnabled => + val menu = new ContextMenu + fillRmbMenu(menu) + root.get.contextMenus.open(menu) + } + + protected def iconBounds: Rect2D = bounds.inflate(-2) + + protected def drawSlotBackground(g: Graphics): Unit = + g.sprite("EmptySlot", bounds) + + protected def drawEmptySlot(g: Graphics): Unit = {} + + protected def drawOccupiedSlot(g: Graphics, item: Item[Element]): Unit = + g.sprite(item.icon, iconBounds) + + final override def draw(g: Graphics): Unit = { + drawSlotBackground(g) + + item match { + case Some(item) => drawOccupiedSlot(g, item) + case None => drawEmptySlot(g) + } + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/TieredSlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/TieredSlotWidget.scala new file mode 100644 index 0000000..4c44091 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/TieredSlotWidget.scala @@ -0,0 +1,14 @@ +package ocelot.desktop.ui.widget.inventory + +import totoro.ocelot.brain.util.Tier + +import scala.language.existentials + +trait TieredSlotWidget[E] extends SlotWidget[E] { + val slotTier: Option[Int] + + protected def isTierAdmissible(itemTier: Int): Boolean = slotTier.forall(slotTier => PartialFunction.cond(itemTier) { + case _ if itemTier <= slotTier => true + case _ if itemTier == Tier.Four && slotTier == Tier.Three => true // creative items fit into T3 slots + }) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/CPUItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/CPUItemPrototype.scala new file mode 100644 index 0000000..d132ef2 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/CPUItemPrototype.scala @@ -0,0 +1,59 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import ocelot.desktop.ui.widget.inventory.item.traits.{EnvironmentItemPrototype, PersistableItemPrototype, SlotTieredItemPrototype} +import totoro.ocelot.brain.entity +import totoro.ocelot.brain.entity.traits.{GenericCPU, Tiered} + +import scala.reflect.ClassTag + +sealed abstract class CPUItemPrototype[E <: GenericCPU](implicit override val elementTag: ClassTag[E]) + extends ItemPrototype.WithInstance + with SlotTieredItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype + with Tiered { + + override type Element = E + + override def slotTier: Int = tier + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance + + // TODO: override fillRmbMenu (set arch) +} + +object CPUItemPrototype { + class CPU(override var tier: Int) extends CPUItemPrototype[entity.CPU] { + override type Item = Instance + + override def name: String = s"CPU ($tierString)" + + override def icon: IconDef = new IconDef(s"items/CPU$tier") + + override def build: Item = new Instance(new entity.CPU(tier)) + } + + class APU(override var tier: Int) extends CPUItemPrototype[entity.APU] { + override type Item = Instance + + override def name: String = s"APU ($tierString)" + + override def icon: IconDef = new IconDef(s"items/APU$tier", animation = APU.Animation) + + // an APU is a tier higher than its base CPU + override def slotTier: Int = tier + 1 + + override def build: Item = new Instance(new entity.APU(tier)) + } + + object APU { + private val Animation = Some(Array( + (0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), + (4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f))) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/DataCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/DataCardItemPrototype.scala new file mode 100644 index 0000000..02b0dfe --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/DataCardItemPrototype.scala @@ -0,0 +1,56 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype, SlotTieredItemPrototype} +import totoro.ocelot.brain.entity.DataCard +import totoro.ocelot.brain.util.Tier + +import scala.reflect.ClassTag + +sealed abstract class DataCardItemPrototype[E <: DataCard](implicit override val elementTag: ClassTag[E]) + extends ItemPrototype.WithInstance + with SlotTieredItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype + with CardItemPrototype { + + override type Element = E + + override def icon: IconDef = new IconDef(s"items/DataCard$slotTier", animation = DataCardItemPrototype.Animation) + + override def name: String = s"Data Card ($tierString)" + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object DataCardItemPrototype { + private final val Animation = Some(Array((0, 4f), (1, 4f), (2, 4f), (3, 4f), (4, 4f), (5, 4f), (6, 4f), (7, 4f))) + + class Tier1 extends DataCardItemPrototype[DataCard.Tier1] { + override type Item = Instance + + override val slotTier: Int = Tier.One + + override def build: Item = new Instance(new DataCard.Tier1) + } + + class Tier2 extends DataCardItemPrototype[DataCard.Tier2] { + override type Item = Instance + + override val slotTier: Int = Tier.Two + + override def build: Item = new Instance(new DataCard.Tier2) + } + + class Tier3 extends DataCardItemPrototype[DataCard.Tier3] { + override type Item = Instance + + override val slotTier: Int = Tier.Three + + override def build: Item = new Instance(new DataCard.Tier3) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/EEPROMItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/EEPROMItemPrototype.scala new file mode 100644 index 0000000..a0968b4 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/EEPROMItemPrototype.scala @@ -0,0 +1,70 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import ocelot.desktop.ui.widget.inventory.item.EEPROMItemPrototype.LootFactories +import ocelot.desktop.ui.widget.inventory.item.traits.{EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.EEPROM +import totoro.ocelot.brain.loot.Loot +import totoro.ocelot.brain.nbt.NBTTagCompound +import totoro.ocelot.brain.workspace.Workspace + +import scala.reflect.{ClassTag, classTag} + +class EEPROMItemPrototype(private var lootFactoryKey: Option[String]) + extends ItemPrototype.WithInstance + with EnvironmentItemPrototype + with PersistableItemPrototype { + + require(lootFactoryKey.forall(EEPROMItemPrototype.LootFactories.contains), + s"unrecognized loot EEPROM key ${lootFactoryKey.get}") + + def this() { + this(None) + } + + override type Element = EEPROM + override type Item = Instance + + override val elementTag: ClassTag[EEPROM] = classTag[EEPROM] + + private def lootFactory: Option[Loot.EEPROMFactory] = lootFactoryKey.map(EEPROMItemPrototype.LootFactories) + + override def icon: IconDef = new IconDef("items/EEPROM") + + override def name: String = lootFactory.map(_.label).getOrElse("EEPROM") + + override def build: Item = + new Instance(lootFactory.map(_.create()).getOrElse(new EEPROM)) + + override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = { + super.load(nbt, workspace) + + lootFactoryKey = Option.when(nbt.hasKey(EEPROMItemPrototype.LootTag)) { + Some(nbt.getString(EEPROMItemPrototype.LootTag)).filter(LootFactories.contains) + }.flatten + } + + override def save(nbt: NBTTagCompound): Unit = { + super.save(nbt) + + lootFactoryKey.foreach(nbt.setString(EEPROMItemPrototype.LootTag, _)) + } + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance + + // TODO: override name in Instance once we add this method to Item (for tooltips) +} + +object EEPROMItemPrototype { + // the key is used for persistence + final val LootFactories = Map( + "lua-bios" -> Loot.LuaBiosEEPROM, + "advloader" -> Loot.AdvLoaderEEPROM, + ) + + private final val LootTag = "loot" +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/FloppyItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/FloppyItemPrototype.scala new file mode 100644 index 0000000..1d4e095 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/FloppyItemPrototype.scala @@ -0,0 +1,112 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.item.FloppyItemPrototype.Managed.LootFactories +import ocelot.desktop.ui.widget.inventory.item.traits.{EnvironmentItemPrototype, PersistableItemPrototype} +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import totoro.ocelot.brain.entity.traits.Floppy +import totoro.ocelot.brain.entity.{FloppyManaged, FloppyUnmanaged} +import totoro.ocelot.brain.loot.Loot +import totoro.ocelot.brain.nbt.NBTTagCompound +import totoro.ocelot.brain.util.DyeColor +import totoro.ocelot.brain.workspace.Workspace + +import scala.reflect.ClassTag + +sealed abstract class FloppyItemPrototype[E <: Floppy](var color: DyeColor) + (implicit override val elementTag: ClassTag[E]) + extends ItemPrototype.WithInstance + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = E + + override def icon: IconDef = new IconDef(s"items/FloppyDisk_${color.name}") + + override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = { + super.load(nbt, workspace) + color = DyeColor.byCode(nbt.getInteger(FloppyItemPrototype.ColorTag)) + } + + override def save(nbt: NBTTagCompound): Unit = { + super.save(nbt) + nbt.setInteger(FloppyItemPrototype.ColorTag, color.code) + } + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance + + // TODO: override name in Instance once we add this method to Item (for tooltips) +} + +object FloppyItemPrototype { + class Managed(initialColor: DyeColor, + private var lootFactoryKey: Option[String]) extends FloppyItemPrototype[FloppyManaged](initialColor) { + def this() { + this(DyeColor.GRAY, None) + } + + def this(lootFactoryKey: String) { + this(DyeColor.GRAY, Some(lootFactoryKey)) + + color = Managed.LootFactories(lootFactoryKey).color + } + + require(lootFactoryKey.forall(Managed.LootFactories.contains), + s"unrecognized loot floppy key ${lootFactoryKey.get}") + + override type Item = Instance + + private def lootFactory: Option[Loot.FloppyFactory] = lootFactoryKey.map(Managed.LootFactories) + + override def name: String = lootFactory.map(_.name).getOrElse("Floppy Disk") + + override def build: Item = + new Instance(lootFactory.map(_.create()) + .getOrElse(new FloppyManaged(name, color))) + + override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = { + super.load(nbt, workspace) + + lootFactoryKey = Option.when(nbt.hasKey(Managed.LootTag)) { + Some(nbt.getString(Managed.LootTag)).filter(LootFactories.contains) + }.flatten + } + + override def save(nbt: NBTTagCompound): Unit = { + super.save(nbt) + + lootFactoryKey.foreach(nbt.setString(Managed.LootTag, _)) + } + } + + object Managed { + final val LootFactories = Map( + "network" -> Loot.NetworkFloppy, + "plan9k" -> Loot.Plan9kFloppy, + "irc" -> Loot.IrcFloppy, + "openloader" -> Loot.OpenLoaderFloppy, + "openos" -> Loot.OpenOsFloppy, + "oppm" -> Loot.OPPMFloppy, + "data" -> Loot.DataFloppy, + ) + + private final val LootTag = "loot" + } + + class Unmanaged(initialColor: DyeColor) extends FloppyItemPrototype[FloppyUnmanaged](initialColor) { + def this() { + this(DyeColor.GRAY) + } + + override type Item = Instance + + override def name: String = "Floppy Disk (unmanaged)" + + override def build: Item = new Instance(new FloppyUnmanaged(name, color)) + } + + private final val ColorTag = "color" +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/GraphicsCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/GraphicsCardItemPrototype.scala new file mode 100644 index 0000000..7ccb07b --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/GraphicsCardItemPrototype.scala @@ -0,0 +1,37 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import totoro.ocelot.brain.entity.GraphicsCard +import totoro.ocelot.brain.entity.traits.Tiered + +import scala.reflect.{ClassTag, classTag} + +class GraphicsCardItemPrototype(override var tier: Int) + extends ItemPrototype.WithInstance + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype + with Tiered { + + require((0 to 2).contains(tier), s"Unsupported GPU tier: $tier") + + override type Element = GraphicsCard + override type Item = Instance + + override val elementTag: ClassTag[GraphicsCard] = classTag[GraphicsCard] + + override def slotTier: Int = tier + + override def name: String = s"Graphics Card ($tierString)" + + override def icon: IconDef = new IconDef(s"items/GraphicsCard$tier") + + override def build: Item = new Instance(new GraphicsCard(tier)) + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/HDDItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/HDDItemPrototype.scala new file mode 100644 index 0000000..90458f2 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/HDDItemPrototype.scala @@ -0,0 +1,47 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{EnvironmentItemPrototype, PersistableItemPrototype, SlotTieredItemPrototype} +import totoro.ocelot.brain.entity.{HDDManaged, HDDUnmanaged} +import totoro.ocelot.brain.entity.traits.{Disk, Tiered} + +import scala.reflect.ClassTag + +sealed abstract class HDDItemPrototype[E <: Disk](implicit override val elementTag: ClassTag[E]) + extends ItemPrototype.WithInstance + with SlotTieredItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype + with Tiered { + + override type Element = E + + override def icon: IconDef = new IconDef(s"items/HardDiskDrive$slotTier") + + override def slotTier: Int = tier + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object HDDItemPrototype { + class Managed(override var tier: Int) extends HDDItemPrototype[HDDManaged] { + override type Item = Instance + + override def name: String = s"HDD ($tierString)" + + override def build: Item = new Instance(new HDDManaged(tier)) + } + + class Unmanaged(override var tier: Int) extends HDDItemPrototype[HDDUnmanaged] { + override type Item = Instance + + override def name: String = s"HDD ($tierString, unmanaged)" + + // TODO: figure out what should be fed to name + override def build: Item = new Instance(new HDDUnmanaged(tier, name = null)) + } +} \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/InternetCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/InternetCardItemPrototype.scala new file mode 100644 index 0000000..9c795da --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/InternetCardItemPrototype.scala @@ -0,0 +1,39 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.InternetCard +import totoro.ocelot.brain.util.Tier + +import scala.reflect.{ClassTag, classTag} + +class InternetCardItemPrototype + extends ItemPrototype + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = InternetCard + override type Item = Instance + + override val elementTag: ClassTag[InternetCard] = classTag[InternetCard] + + override val slotTier: Int = Tier.Two + + override def icon: IconDef = new IconDef("items/InternetCard", animation = InternetCardItemPrototype.Animation) + + override val name: String = "Internet Card" + + override def build: Item = new Instance(new InternetCard) + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object InternetCardItemPrototype { + private final val Animation = + Some(Array((0, 2f), (1, 7f), (0, 5f), (1, 4f), (0, 7f), (1, 2f), (0, 8f), (1, 9f), (0, 6f), (1, 4f))) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/LinkedCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/LinkedCardItemPrototype.scala new file mode 100644 index 0000000..d87c7b0 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/LinkedCardItemPrototype.scala @@ -0,0 +1,38 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.LinkedCard +import totoro.ocelot.brain.util.Tier + +import scala.reflect.{ClassTag, classTag} + +class LinkedCardItemPrototype + extends ItemPrototype + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = LinkedCard + override type Item = Instance + + override val elementTag: ClassTag[LinkedCard] = classTag[LinkedCard] + + override val slotTier: Int = Tier.Three + + override def icon: IconDef = new IconDef("items/LinkedCard", animation = LinkedCardItemPrototype.Animation) + + override val name: String = "Linked Card" + + override def build: Item = new Instance(new LinkedCard) + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object LinkedCardItemPrototype { + private final val Animation = Some(Array((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f))) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/MemoryItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/MemoryItemPrototype.scala new file mode 100644 index 0000000..36a94fb --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/MemoryItemPrototype.scala @@ -0,0 +1,47 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{EnvironmentItemPrototype, PersistableItemPrototype, SlotTieredItemPrototype} +import totoro.ocelot.brain.entity.Memory +import totoro.ocelot.brain.entity.traits.Tiered + +import scala.reflect.{ClassTag, classTag} + +class MemoryItemPrototype(var tier: Int) + extends ItemPrototype + with SlotTieredItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype + with Tiered { + + require((1 to 6).contains(tier), s"Unsupported memory tier: $tier") + + override type Element = Memory + override type Item = Instance + + override val elementTag: ClassTag[Memory] = classTag[Memory] + + override def slotTier: Int = tier / 2 + + override def icon: IconDef = new IconDef(s"items/Memory$tier") + + override protected def tierString: String = MemoryItemPrototype.Tiers(tier) + + override def name: String = s"RAM ($tierString)" + + override def build: Item = new Instance(new Memory(tier)) + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object MemoryItemPrototype { + private final val Tiers = Array( + "Tier 1", "Tier 1.5", + "Tier 2", "Tier 2.5", + "Tier 3", "Tier 3.5", + ) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/NetworkCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/NetworkCardItemPrototype.scala new file mode 100644 index 0000000..9dda4b1 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/NetworkCardItemPrototype.scala @@ -0,0 +1,34 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.NetworkCard +import totoro.ocelot.brain.util.Tier + +import scala.reflect.{ClassTag, classTag} + +class NetworkCardItemPrototype + extends ItemPrototype + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = NetworkCard + override type Item = Instance + + override val elementTag: ClassTag[NetworkCard] = classTag[NetworkCard] + + override val slotTier: Int = Tier.One + + override def icon: IconDef = new IconDef("items/NetworkCard") + + override val name: String = "Network Card" + + override def build: Item = new Instance(new NetworkCard) + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/RedstoneCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/RedstoneCardItemPrototype.scala new file mode 100644 index 0000000..33fd37c --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/RedstoneCardItemPrototype.scala @@ -0,0 +1,48 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.{Item, ItemPrototype} +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.Redstone +import totoro.ocelot.brain.entity.traits.Environment +import totoro.ocelot.brain.util.{Persistable, Tier} + +import scala.reflect.ClassTag + +sealed abstract class RedstoneCardItemPrototype[E <: Environment with Persistable](implicit override val elementTag: ClassTag[E]) + extends ItemPrototype + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = E + + override def icon: IconDef = new IconDef(s"items/RedstoneCard$slotTier") + + override def name: String = s"Redstone Card ($tierString)" + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance + + // TODO: override fillRmbMenu in Instance +} + +object RedstoneCardItemPrototype { + class Tier1 extends RedstoneCardItemPrototype[Redstone.Tier1] { + override type Item = Instance + + override val slotTier: Int = Tier.One + + override def build: Item = new Instance(new Redstone.Tier1) + } + + class Tier2 extends RedstoneCardItemPrototype[Redstone.Tier2] { + override type Item = Instance + + override val slotTier: Int = Tier.Two + + override def build: Item = new Instance(new Redstone.Tier2) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/WirelessNetworkCardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/WirelessNetworkCardItemPrototype.scala new file mode 100644 index 0000000..417e399 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/WirelessNetworkCardItemPrototype.scala @@ -0,0 +1,45 @@ +package ocelot.desktop.ui.widget.inventory.item + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import ocelot.desktop.ui.widget.inventory.item.traits.{CardItemPrototype, EnvironmentItemPrototype, PersistableItemPrototype} +import totoro.ocelot.brain.entity.WirelessNetworkCard +import totoro.ocelot.brain.util.Tier + +import scala.reflect.ClassTag + +sealed abstract class WirelessNetworkCardItemPrototype[E <: WirelessNetworkCard](implicit override val elementTag: ClassTag[E]) + extends ItemPrototype + with CardItemPrototype + with EnvironmentItemPrototype + with PersistableItemPrototype { + + override type Element = E + + override def icon: IconDef = new IconDef(s"items/WirelessNetworkCard$slotTier") + + override def name: String = s"Wireless Network Card ($tierString)" + + class Instance(element: Element) + extends super.Instance(element) + with super[EnvironmentItemPrototype].ExtendedInstance + with super[PersistableItemPrototype].ExtendedInstance +} + +object WirelessNetworkCardItemPrototype { + class Tier1 extends WirelessNetworkCardItemPrototype[WirelessNetworkCard.Tier1] { + override type Item = Instance + + override val slotTier: Int = Tier.One + + override def build: Item = new Instance(new WirelessNetworkCard.Tier1) + } + + class Tier2 extends WirelessNetworkCardItemPrototype[WirelessNetworkCard.Tier2] { + override type Item = Instance + + override val slotTier: Int = Tier.Two + + override def build: Item = new Instance(new WirelessNetworkCard.Tier2) + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/CardItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/CardItemPrototype.scala new file mode 100644 index 0000000..ba0befe --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/CardItemPrototype.scala @@ -0,0 +1,7 @@ +package ocelot.desktop.ui.widget.inventory.item.traits + +import ocelot.desktop.ui.widget.inventory.ItemPrototype + +trait CardItemPrototype extends SlotTieredItemPrototype { + this: ItemPrototype => +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/EnvironmentItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/EnvironmentItemPrototype.scala new file mode 100644 index 0000000..8869748 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/EnvironmentItemPrototype.scala @@ -0,0 +1,21 @@ +package ocelot.desktop.ui.widget.inventory.item.traits + +import ocelot.desktop.ui.UiHandler +import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import totoro.ocelot.brain.entity.traits.Environment + +trait EnvironmentItemPrototype extends ItemPrototype.WithInstance { + override type Element <: Environment + override type Item <: ExtendedInstance + + trait ExtendedInstance extends super.Instance { + override def fillRmbMenu(menu: ContextMenu): Unit = { + super.fillRmbMenu(menu) + + menu.addEntry(new ContextMenuEntry("Copy address", () => { + UiHandler.clipboard = element.node.address + })) + } + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/PersistableItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/PersistableItemPrototype.scala new file mode 100644 index 0000000..221169a --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/PersistableItemPrototype.scala @@ -0,0 +1,47 @@ +package ocelot.desktop.ui.widget.inventory.item.traits + +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import totoro.ocelot.brain.nbt.NBTTagCompound +import totoro.ocelot.brain.nbt.persistence.NBTPersistence +import totoro.ocelot.brain.util.Persistable +import totoro.ocelot.brain.workspace.Workspace + +trait PersistableItemPrototype extends ItemPrototype.WithInstance with Persistable { + proto => + + override type Element <: Persistable + + trait ExtendedInstance extends super.Instance with Persistable { + override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = { + super.load(nbt, workspace) + + val elementNbt = nbt.getCompoundTag(PersistableItemPrototype.ElementTag) + element = NBTPersistence.load(elementNbt, workspace).asInstanceOf[Element] + } + + override def save(nbt: NBTTagCompound): Unit = { + super.save(nbt) + + nbt.setTag(PersistableItemPrototype.ElementTag, NBTPersistence.save(element)) + nbt.setTag(PersistableItemPrototype.PrototypeTag, NBTPersistence.save(proto)) + } + } +} + +object PersistableItemPrototype { + private final val ElementTag = "element" + private final val PrototypeTag = "prototype" + + class ItemConstructor extends NBTPersistence.InstanceConstructor { + override def construct(nbt: NBTTagCompound, className: String, workspace: Workspace): Persistable = { + val data = nbt.getCompoundTag(NBTPersistence.DataTag) + val prototypeNbt = data.getCompoundTag(PrototypeTag) + val prototype = NBTPersistence.load(prototypeNbt, workspace).asInstanceOf[ItemPrototype] + val clazz = Class.forName(className).asSubclass(classOf[PersistableItemPrototype#ExtendedInstance]) + val item = clazz.cast(prototype.build) + item.load(data, workspace) + + item + } + } +} \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/SlotTieredItemPrototype.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/SlotTieredItemPrototype.scala new file mode 100644 index 0000000..744fa29 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/SlotTieredItemPrototype.scala @@ -0,0 +1,14 @@ +package ocelot.desktop.ui.widget.inventory.item.traits + +import ocelot.desktop.ui.widget.inventory.ItemPrototype +import totoro.ocelot.brain.util.Tier + +trait SlotTieredItemPrototype { + this: ItemPrototype => + + def slotTier: Int + + protected def tierString: String = + if (slotTier == Tier.Four) "Creative" + else s"Tier $slotTier" +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/AnySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/AnySlot.scala new file mode 100644 index 0000000..b9aae28 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/AnySlot.scala @@ -0,0 +1,8 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.{ItemPrototype, SlotWidget} + +class AnySlot(workspaceView: WorkspaceView) extends SlotWidget[AnyRef](workspaceView) { + override def accepts(prototype: ItemPrototype): Boolean = true +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CPUSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CPUSlot.scala new file mode 100644 index 0000000..7f25573 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CPUSlot.scala @@ -0,0 +1,49 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.node.nodes.ComputerNode +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.item.CPUItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, ItemPrototype, LmbItemSelectionSlot} +import totoro.ocelot.brain.entity.traits.{Entity, GenericCPU, Inventory} + +class CPUSlot(owner: Inventory#Slot, node: ComputerNode, val tier: Int, workspaceView: WorkspaceView) + extends InventorySlotWidget[GenericCPU with Entity](owner, workspaceView) + with DecoratedSlotWidget[GenericCPU with Entity] + with LmbItemSelectionSlot[GenericCPU with Entity] { + + override val slotTier: Option[Int] = Some(tier) + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/CPU")) + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case cpu: CPUItemPrototype[_] if isTierAdmissible(cpu.slotTier) => true + } + + // TODO: move to CPUItemPrototype +// override def fillRmbMenu(menu: ContextMenu): Unit = { +// if (item.isEmpty) return +// +// val cpu = item.get +// +// menu.addEntry(new ContextMenuSubmenu("Set architecture") { +// for (arch <- cpu.allArchitectures) { +// val name = MachineAPI.getArchitectureName(arch) + +// (if (arch == cpu.architecture) " (current)" else "") +// +// addEntry(new ContextMenuEntry(name, () => { +// val machine = node.computer.machine +// if (machine.isRunning) { +// machine.stop() +// cpu.setArchitecture(arch) +// machine.start() +// } else { +// cpu.setArchitecture(arch) +// } +// })) +// } +// }) +// +// super.fillRmbMenu(menu) +// } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CardRegistry.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardRegistry.scala similarity index 98% rename from src/main/scala/ocelot/desktop/ui/widget/slot/CardRegistry.scala rename to src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardRegistry.scala index f18d615..689e2f6 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CardRegistry.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardRegistry.scala @@ -1,4 +1,4 @@ -package ocelot.desktop.ui.widget.slot +package ocelot.desktop.ui.widget.inventory.slot import ocelot.desktop.graphics.IconDef import totoro.ocelot.brain.entity.traits.{Entity, Tiered} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardSlot.scala new file mode 100644 index 0000000..be84e20 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardSlot.scala @@ -0,0 +1,36 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.inventory.item.traits.CardItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, ItemPrototype, LmbItemSelectionSlot} +import ocelot.desktop.ui.widget.{WorkspaceView, inventory} +import totoro.ocelot.brain.entity.traits.{Entity, Inventory} +import totoro.ocelot.brain.util.Persistable + +class CardSlot(slave: Inventory#Slot, val tier: Int, workspaceView: WorkspaceView) + extends InventorySlotWidget[Entity with Persistable](slave, workspaceView) + with DecoratedSlotWidget[Entity with Persistable] + with LmbItemSelectionSlot[Entity with Persistable] { + + override val slotTier: Option[Int] = Some(tier) + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/Card")) + + // TODO: move the code below to Item[Redstone] +// override def fillRmbMenu(menu: ContextMenu): Unit = { +// val pool = UiHandler.root.workspaceView.windowPool +// item match { +// case Some(card: Redstone.Tier2) => +// menu.addEntry(new ContextMenuEntry("Redstone I/O", () => pool.openWindow(new Redstone1Window(card)))) +// menu.addEntry(new ContextMenuEntry("Bundled I/O", () => pool.openWindow(new Redstone2Window(card)))) +// case Some(card: Redstone.Tier1) => +// menu.addEntry(new ContextMenuEntry("Redstone I/O", () => pool.openWindow(new Redstone1Window(card)))) +// case _ => +// } +// super.fillRmbMenu(menu) +// } + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case card: CardItemPrototype if isTierAdmissible(card.slotTier) => true + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/DiskSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/DiskSlot.scala new file mode 100644 index 0000000..dab4f4f --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/DiskSlot.scala @@ -0,0 +1,22 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.item.HDDItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, ItemPrototype, LmbItemSelectionSlot} +import totoro.ocelot.brain.entity.HDDManaged +import totoro.ocelot.brain.entity.traits.Inventory + +class DiskSlot(owner: Inventory#Slot, val tier: Int, workspaceView: WorkspaceView) + extends InventorySlotWidget[HDDManaged](owner, workspaceView) + with DecoratedSlotWidget[HDDManaged] + with LmbItemSelectionSlot[HDDManaged] { + + override val slotTier: Option[Int] = Some(tier) + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/HDD")) + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case hdd: HDDItemPrototype[_] if isTierAdmissible(hdd.slotTier) => true + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/EEPROMSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/EEPROMSlot.scala new file mode 100644 index 0000000..50a372d --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/EEPROMSlot.scala @@ -0,0 +1,24 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.item.EEPROMItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, ItemPrototype, LmbItemSelectionSlot} +import totoro.ocelot.brain.entity.EEPROM +import totoro.ocelot.brain.entity.traits.Inventory + +class EEPROMSlot(owner: Inventory#Slot, workspaceView: WorkspaceView) + extends InventorySlotWidget[EEPROM](owner, workspaceView) + with DecoratedSlotWidget[EEPROM] + with LmbItemSelectionSlot[EEPROM] { + + override type Element = EEPROM + + override val slotTier: Option[Int] = None + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/EEPROM")) + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case _: EEPROMItemPrototype => true + } +} \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/FloppySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/FloppySlot.scala new file mode 100644 index 0000000..c56e89d --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/FloppySlot.scala @@ -0,0 +1,36 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.audio.{Audio, SoundSource} +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.item.FloppyItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, Item, ItemPrototype, LmbItemSelectionSlot} +import totoro.ocelot.brain.entity.traits.{Floppy, Inventory} + +class FloppySlot(owner: Inventory#Slot, workspaceView: WorkspaceView) + extends InventorySlotWidget[Floppy](owner, workspaceView) + with DecoratedSlotWidget[Floppy] + with LmbItemSelectionSlot[Floppy] { + + private val soundFloppyInsert = new SoundSource(Audio.FloppyInsert) + private val soundFloppyEject = new SoundSource(Audio.FloppyEject) + + override val slotTier: Option[Int] = None + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/Floppy")) + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case _: FloppyItemPrototype[_] => true + } + + override def onAdded(item: Item[Element]): Unit = { + super.onAdded(item) + + soundFloppyInsert.play() + } + + override def onRemoved(item: Item[Element]): Unit = { + super.onRemoved(item) + soundFloppyEject.play() + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/MemorySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/MemorySlot.scala new file mode 100644 index 0000000..0a2fcbc --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/inventory/slot/MemorySlot.scala @@ -0,0 +1,22 @@ +package ocelot.desktop.ui.widget.inventory.slot + +import ocelot.desktop.graphics.IconDef +import ocelot.desktop.ui.widget.WorkspaceView +import ocelot.desktop.ui.widget.inventory.item.MemoryItemPrototype +import ocelot.desktop.ui.widget.inventory.{DecoratedSlotWidget, InventorySlotWidget, ItemPrototype, LmbItemSelectionSlot} +import totoro.ocelot.brain.entity.Memory +import totoro.ocelot.brain.entity.traits.Inventory + +class MemorySlot(slave: Inventory#Slot, val tier: Int, workspaceView: WorkspaceView) + extends InventorySlotWidget[Memory](slave, workspaceView) + with DecoratedSlotWidget[Memory] + with LmbItemSelectionSlot[Memory] { + + override val slotTier: Option[Int] = Some(tier) + + override def bgIcon: Option[IconDef] = Some(new IconDef("icons/Memory")) + + override def accepts(prototype: ItemPrototype): Boolean = PartialFunction.cond(prototype) { + case memory: MemoryItemPrototype if isTierAdmissible(memory.slotTier) => true + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala deleted file mode 100644 index 76d2959..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala +++ /dev/null @@ -1,88 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.node.nodes.ComputerNode -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu} -import totoro.ocelot.brain.entity.machine.MachineAPI -import totoro.ocelot.brain.entity.traits.{Entity, GenericCPU, Inventory} -import totoro.ocelot.brain.entity.{APU, CPU} -import totoro.ocelot.brain.util.Tier - -class CPUSlot(owner: Inventory#Slot, node: ComputerNode, val tier: Int) extends InventorySlot[GenericCPU with Entity](owner) { - private val APUAnimation = Some(Array( - (0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), - (4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f))) - - override def itemIcon: Option[IconDef] = item match { - case Some(cpu: CPU) => Some(new IconDef("items/CPU" + cpu.tier)) - case Some(apu: APU) => Some(new IconDef("items/APU" + apu.tier, animation = APUAnimation)) - case _ => None - } - - override def icon: Option[IconDef] = Some(new IconDef("icons/CPU")) - override def tierIcon: Option[IconDef] = Some(new IconDef("icons/Tier" + tier)) - - override def fillLmbMenu(menu: ContextMenu): Unit = { - menu.addEntry(new ContextMenuEntry("CPU (Tier 1)", - () => item = new CPU(Tier.One), - icon = Some(new IconDef("items/CPU0")))) - - if (tier >= Tier.Two) { - menu.addEntry(new ContextMenuEntry("CPU (Tier 2)", - () => item = new CPU(Tier.Two), - icon = Some(new IconDef("items/CPU1")))) - } - - if (tier >= Tier.Three) { - menu.addEntry(new ContextMenuEntry("CPU (Tier 3)", - () => item = new CPU(Tier.Three), - icon = Some(new IconDef("items/CPU2")))) - } - - if (tier < Tier.Two) return - - menu.addEntry(new ContextMenuSubmenu("APU (CPU + GPU)", Some(new IconDef("items/GraphicsCard1"))) { - addEntry(new ContextMenuEntry("Tier 2", - () => item = new APU(Tier.One), - icon = Some(new IconDef("items/APU0", animation = APUAnimation)))) - - if (tier >= Tier.Three) { - addEntry(new ContextMenuEntry("Tier 3", - () => item = new APU(Tier.Two), - icon = Some(new IconDef("items/APU1", animation = APUAnimation)))) - - addEntry(new ContextMenuEntry("Creative", - () => item = new APU(Tier.Three), - icon = Some(new IconDef("items/APU2", animation = APUAnimation)))) - } - }) - } - - override def fillRmbMenu(menu: ContextMenu): Unit = { - if (item.isEmpty) return - - val cpu = item.get - - menu.addEntry(new ContextMenuSubmenu("Set architecture") { - for (arch <- cpu.allArchitectures) { - val name = MachineAPI.getArchitectureName(arch) + - (if (arch == cpu.architecture) " (current)" else "") - - addEntry(new ContextMenuEntry(name, () => { - val machine = node.computer.machine - if (machine.isRunning) { - machine.stop() - cpu.setArchitecture(arch) - machine.start() - } else { - cpu.setArchitecture(arch) - } - })) - } - }) - - super.fillRmbMenu(menu) - } - - override def lmbMenuEnabled: Boolean = true -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala deleted file mode 100644 index 436d794..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala +++ /dev/null @@ -1,62 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.ui.UiHandler -import ocelot.desktop.ui.widget.card.{Redstone1Window, Redstone2Window} -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu} -import totoro.ocelot.brain.entity.Redstone -import totoro.ocelot.brain.entity.traits.{Entity, Inventory} -import totoro.ocelot.brain.util.Tier - -class CardSlot(owner: Inventory#Slot, val tier: Int) extends InventorySlot[Entity](owner) { - override def itemIcon: Option[IconDef] = item.flatMap(it => CardRegistry.getIcon(it)) - - override def icon: Option[IconDef] = Some(new IconDef("icons/Card")) - override def tierIcon: Option[IconDef] = Some(new IconDef("icons/Tier" + tier)) - - override def fillRmbMenu(menu: ContextMenu): Unit = { - val pool = UiHandler.root.workspaceView.windowPool - item match { - case Some(card: Redstone.Tier2) => - menu.addEntry(new ContextMenuEntry("Redstone I/O", () => pool.openWindow(new Redstone1Window(card)))) - menu.addEntry(new ContextMenuEntry("Bundled I/O", () => pool.openWindow(new Redstone2Window(card)))) - case Some(card: Redstone.Tier1) => - menu.addEntry(new ContextMenuEntry("Redstone I/O", () => pool.openWindow(new Redstone1Window(card)))) - case _ => - } - super.fillRmbMenu(menu) - } - - override def fillLmbMenu(menu: ContextMenu): Unit = { - val entries = CardRegistry.entries - - var i = 0 - while (i < entries.length) { - val entry = entries(i) - val nextEntry = entries.lift(i + 1) - - if (nextEntry.isDefined && nextEntry.get.name == entry.name) { - val groupName = entry.name - if (tier >= entry.tier) { - menu.addEntry(new ContextMenuSubmenu(groupName, Some(nextEntry.get.icon)) { - entries.view.slice(i, entries.length).takeWhile(_.name == groupName).foreach(entry => { - val name = if (entry.tier == Tier.Four) "Creative" else "Tier " + (entry.tier + 1) - if (tier >= entry.tier) { - addEntry(new ContextMenuEntry(name, () => item = entry.factory(), Some(entry.icon))) - } - i += 1 - }) - }) - } - } else { - if (tier >= entry.tier) { - menu.addEntry(new ContextMenuEntry(entry.name, () => item = entry.factory(), Some(entry.icon))) - } - - i += 1 - } - } - } - - override def lmbMenuEnabled: Boolean = true -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/DiskSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/DiskSlot.scala deleted file mode 100644 index e938f48..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/DiskSlot.scala +++ /dev/null @@ -1,34 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} -import totoro.ocelot.brain.entity.HDDManaged -import totoro.ocelot.brain.entity.traits.Inventory -import totoro.ocelot.brain.util.Tier - -class DiskSlot(owner: Inventory#Slot, val tier: Int) extends InventorySlot[HDDManaged](owner) { - override def itemIcon: Option[IconDef] = item.map(disk => new IconDef("items/HardDiskDrive" + disk.tier)) - - override def icon: Option[IconDef] = Some(new IconDef("icons/HDD")) - override def tierIcon: Option[IconDef] = Some(new IconDef("icons/Tier" + tier)) - - override def fillLmbMenu(menu: ContextMenu): Unit = { - menu.addEntry(new ContextMenuEntry("HDD (Tier 1)", - () => item = new HDDManaged(Tier.One), - icon = Some(new IconDef("items/HardDiskDrive0")))) - - if (tier >= Tier.Two) { - menu.addEntry(new ContextMenuEntry("HDD (Tier 2)", - () => item = new HDDManaged(Tier.Two), - icon = Some(new IconDef("items/HardDiskDrive1")))) - } - - if (tier >= Tier.Three) { - menu.addEntry(new ContextMenuEntry("HDD (Tier 3)", - () => item = new HDDManaged(Tier.Three), - icon = Some(new IconDef("items/HardDiskDrive2")))) - } - } - - override def lmbMenuEnabled: Boolean = true -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/EEPROMSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/EEPROMSlot.scala deleted file mode 100644 index 1a561ca..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/EEPROMSlot.scala +++ /dev/null @@ -1,28 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.node.nodes.ComputerNode -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} -import totoro.ocelot.brain.entity.EEPROM -import totoro.ocelot.brain.entity.traits.Inventory -import totoro.ocelot.brain.loot.Loot - -class EEPROMSlot(owner: Inventory#Slot) extends InventorySlot[EEPROM](owner) { - override def itemIcon: Option[IconDef] = item.map(_ => new IconDef("items/EEPROM")) - override def icon: Option[IconDef] = Some(new IconDef("icons/EEPROM")) - - override def fillLmbMenu(menu: ContextMenu): Unit = { - menu.addEntry(new ContextMenuEntry("Lua BIOS", - () => item = Loot.OpenOsEEPROM.create(), - icon = Some(new IconDef("items/EEPROM")))) - menu.addEntry(new ContextMenuEntry("AdvLoader", - () => item = Loot.AdvLoaderEEPROM.create(), - icon = Some(new IconDef("items/EEPROM")))) - menu.addEntry(new ContextMenuEntry("Empty", - () => item = new EEPROM, - icon = Some(new IconDef("items/EEPROM")))) - - } - - override def lmbMenuEnabled: Boolean = true -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/EntitySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/EntitySlot.scala deleted file mode 100644 index 55083f8..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/EntitySlot.scala +++ /dev/null @@ -1,9 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import totoro.ocelot.brain.entity.traits.{Entity, Inventory} - -class EntitySlot(owner: Inventory#Slot) extends InventorySlot[Entity](owner) { - // TODO - override def icon: Option[IconDef] = None -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/FloppySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/FloppySlot.scala deleted file mode 100644 index 029dd6c..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/FloppySlot.scala +++ /dev/null @@ -1,48 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.audio.{Audio, SoundSource} -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} -import totoro.ocelot.brain.entity.FloppyManaged -import totoro.ocelot.brain.entity.traits.{Floppy, Inventory} -import totoro.ocelot.brain.loot.Loot -import totoro.ocelot.brain.util.DyeColor - -class FloppySlot(owner: Inventory#Slot) extends InventorySlot[Floppy](owner) { - private val soundFloppyInsert = new SoundSource(Audio.FloppyInsert) - private val soundFloppyEject = new SoundSource(Audio.FloppyEject) - - override def itemIcon: Option[IconDef] = item.map(fl => new IconDef("items/FloppyDisk_" + fl.color.name)) - - override def icon: Option[IconDef] = Some(new IconDef("icons/Floppy")) - - override def fillLmbMenu(menu: ContextMenu): Unit = { - for (f <- FloppySlot.FloppyFactories) { - val floppy = f.create() - menu.addEntry(new ContextMenuEntry(floppy.label.getLabel, - () => item = floppy, - icon = Some(new IconDef("items/FloppyDisk_" + floppy.color.name)))) - } - - menu.addEntry(new ContextMenuEntry("Empty", - () => item = new FloppyManaged("Floppy Disk", DyeColor.GRAY), - icon = Some(new IconDef("items/FloppyDisk_dyeGray")))) - } - - override def lmbMenuEnabled: Boolean = true - - override def onAdded(item: Floppy): Unit = { - super.onAdded(item) - soundFloppyInsert.play() - } - - override def onRemoved(item: Floppy): Unit = { - super.onRemoved(item) - soundFloppyEject.play() - } -} - -object FloppySlot { - private val FloppyFactories = Array(Loot.OpenOsFloppy, Loot.Plan9kFloppy, Loot.OPPMFloppy, - Loot.OpenLoaderFloppy, Loot.NetworkFloppy, Loot.IrcFloppy, Loot.DataFloppy) -} \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/InventorySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/InventorySlot.scala deleted file mode 100644 index f5dc548..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/InventorySlot.scala +++ /dev/null @@ -1,26 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.ui.UiHandler -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} -import totoro.ocelot.brain.entity.traits.{Entity, Environment, Inventory} - -abstract class InventorySlot[T <: Entity](val owner: Inventory#Slot) extends SlotWidget[T] { - reloadItem() - - override def onAdded(item: T): Unit = owner.put(item) - - override def onRemoved(item: T): Unit = owner.remove() - - override def fillRmbMenu(menu: ContextMenu): Unit = { - item match { - case Some(env: Environment) => - menu.addEntry(new ContextMenuEntry("Copy address", () => { - UiHandler.clipboard = env.node.address - })) - case _ => - } - super.fillRmbMenu(menu) - } - - def reloadItem(): Unit = _item = owner.get.map(_.asInstanceOf[T]) -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/MemorySlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/MemorySlot.scala deleted file mode 100644 index a99006f..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/MemorySlot.scala +++ /dev/null @@ -1,47 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.graphics.IconDef -import ocelot.desktop.node.nodes.ComputerNode -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} -import totoro.ocelot.brain.entity.Memory -import totoro.ocelot.brain.entity.traits.Inventory -import totoro.ocelot.brain.util.Tier - -class MemorySlot(owner: Inventory#Slot, val tier: Int) extends InventorySlot[Memory](owner) { - override def itemIcon: Option[IconDef] = item.map(mem => new IconDef("items/Memory" + mem.tier)) - - override def icon: Option[IconDef] = Some(new IconDef("icons/Memory")) - override def tierIcon: Option[IconDef] = Some(new IconDef("icons/Tier" + tier)) - - override def fillLmbMenu(menu: ContextMenu): Unit = { - menu.addEntry(new ContextMenuEntry("RAM (Tier 1)", - () => item = new Memory(Tier.One), - icon = Some(new IconDef("items/Memory0")))) - - menu.addEntry(new ContextMenuEntry("RAM (Tier 1.5)", - () => item = new Memory(Tier.Two), - icon = Some(new IconDef("items/Memory1")))) - - if (tier >= Tier.Two) { - menu.addEntry(new ContextMenuEntry("RAM (Tier 2)", - () => item = new Memory(Tier.Three), - icon = Some(new IconDef("items/Memory2")))) - - menu.addEntry(new ContextMenuEntry("RAM (Tier 2.5)", - () => item = new Memory(Tier.Four), - icon = Some(new IconDef("items/Memory3")))) - } - - if (tier >= Tier.Three) { - menu.addEntry(new ContextMenuEntry("RAM (Tier 3)", - () => item = new Memory(Tier.Five), - icon = Some(new IconDef("items/Memory4")))) - - menu.addEntry(new ContextMenuEntry("RAM (Tier 3.5)", - () => item = new Memory(Tier.Six), - icon = Some(new IconDef("items/Memory5")))) - } - } - - override def lmbMenuEnabled: Boolean = true -} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/SlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/SlotWidget.scala deleted file mode 100644 index 99068b6..0000000 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/SlotWidget.scala +++ /dev/null @@ -1,75 +0,0 @@ -package ocelot.desktop.ui.widget.slot - -import ocelot.desktop.geometry.Size2D -import ocelot.desktop.graphics.{Graphics, IconDef} -import ocelot.desktop.ui.event.handlers.ClickHandler -import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} -import ocelot.desktop.ui.widget.Widget -import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} - -abstract class SlotWidget[T] extends Widget with ClickHandler { - def icon: Option[IconDef] - - def tierIcon: Option[IconDef] = None - - def itemIcon: Option[IconDef] = None - - def onRemoved(item: T): Unit = {} - - def onAdded(item: T): Unit = {} - - def fillLmbMenu(menu: ContextMenu): Unit = {} - - def fillRmbMenu(menu: ContextMenu): Unit = { - menu.addEntry(new ContextMenuEntry("Remove", () => item = None)) - } - - def lmbMenuEnabled: Boolean = false - - def rmbMenuEnabled: Boolean = _item.isDefined - - final var _item: Option[T] = None - - final def item_=(v: Option[T]): Unit = { - if (_item.isDefined) - onRemoved(_item.get) - - _item = v - - if (v.isDefined) - onAdded(v.get) - } - - final def item_=(v: T): Unit = item = Some(v) - - final def item: Option[T] = _item - - final override def minimumSize: Size2D = Size2D(36, 36) - final override def maximumSize: Size2D = minimumSize - - final override def receiveMouseEvents: Boolean = true - - eventHandlers += { - case ClickEvent(MouseEvent.Button.Left, _) if lmbMenuEnabled => - val menu = new ContextMenu - fillLmbMenu(menu) - root.get.contextMenus.open(menu) - case ClickEvent(MouseEvent.Button.Right, _) if rmbMenuEnabled => - val menu = new ContextMenu - fillRmbMenu(menu) - root.get.contextMenus.open(menu) - } - - final override def draw(g: Graphics): Unit = { - g.sprite("EmptySlot", bounds) - - val iconBounds = bounds.inflate(-2) - - itemIcon match { - case Some(icon) => g.sprite(icon, iconBounds) - case None => - tierIcon.foreach(g.sprite(_, iconBounds)) - icon.foreach(g.sprite(_, iconBounds)) - } - } -} diff --git a/src/main/scala/ocelot/desktop/util/TrayInventory.scala b/src/main/scala/ocelot/desktop/util/TrayInventory.scala deleted file mode 100644 index 486b83c..0000000 --- a/src/main/scala/ocelot/desktop/util/TrayInventory.scala +++ /dev/null @@ -1,7 +0,0 @@ -package ocelot.desktop.util - -import totoro.ocelot.brain.entity.traits.Inventory - -class TrayInventory extends Inventory { - // TODO add something here? -} diff --git a/src/main/scala/ocelot/desktop/util/package.scala b/src/main/scala/ocelot/desktop/util/package.scala index 6ee0a73..4f4d6be 100644 --- a/src/main/scala/ocelot/desktop/util/package.scala +++ b/src/main/scala/ocelot/desktop/util/package.scala @@ -3,13 +3,17 @@ package ocelot.desktop import java.util.concurrent.locks.Lock package object util { - final def withLockAcquired[T](lock: Lock, f: () => T): T = { + final def withLockAcquired[T](lock: Lock, f: => T): T = { lock.lock() try { - f() + f } finally { lock.unlock() } } + + implicit class ExtendedTuple2[A, B](tuple: (A, B)) { + def flipped: (B, A) = (tuple._2, tuple._1) + } }