From 04f6e647d40dfc701755c78a3f1eaf5a5aed3fd3 Mon Sep 17 00:00:00 2001 From: Fingercomp Date: Sun, 3 Jul 2022 22:37:47 +0700 Subject: [PATCH] Refactor inventory system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of storing the actual Entity, slots now contain an Item, which is a wrapper type that, aside from the Entity itself, provides its name, icon, and context menu entries. For the sake of generality, the value represented by an Item is called its element (so it doesn't have to an Entity). Each Item keeps a reference to its ItemPrototype, which, broadly speaking, encapsulates its essential characteristics. In Minecraft, ItemPrototype would be what you'd see in the creative mode's item selection tabs, and Item is what you'd store in inventories. In Ocelot Desktop, item selector widget shows a list of prototypes, and slots store items. Previously, slots could store any item whatsoever, but since SlotWidget subclasses only showed accepted entries in the selector and there was no other way of inserting an item into the slot, it worked, uhh, fine. However, this no longer proves adequate if I want to allow moving items between slots. Therefore, the slots are required to accept an item if and only if their `accepts` method returns `true` for the item's prototype. We check prototypes instead of the actual items so that we could populate the item selector list with accepted prototypes without having to build concrete items each time. For convenience, `accepts(Item)` is also defined in `SlotWidget` that delegates to `accepts(ItemPrototype)`; the method is marked `final` to prevent overriding. Another major change introduced by this commit concerns the responsibility for slot persistence. Again, previously, the persistence worked as follows. (InventorySlotWidget is an Ocelot Desktop widget; Inventory is ocelot-brain's trait; and Inventory#Slot is a reference to one of the Inventory's slots.) 1. An InventorySlotWidget was associated with its owning Inventory#Slot. Setting an item in the InventorySlotWidget would set it in the owning Inventory#Slot, and clearing the InventorySlotWidget would clear the Inventory#Slot. 2. The InventorySlotWidget didn't care about persistence whatsoever; it was the responsibility of Inventory, which would save its contents to NBT. 3. When a workspace was loaded, the entity in the Inventory#Slot was restored from NBT, and the InventorySlotWidget would initialize its item to the contents of the Inventory#Slot. 4. The item's icon and UI behavior was determined by the concrete InventorySlotWidget class: for instance, CPUSlot would check whether the item was a CPU or an APU, retrieve its tier, and update the icon accordingly. After the logic described in the 4th point had been moved to Item and ItemPrototype, a problem has appeared: an Item instance had be to be restored from the item's element. This commit now moves the burden of saving/loading SlotWidget contents to Ocelot Desktop — more specifically, the SlotGroup and InventorySlotGroup. (Accordingly, an InventorySlotWidget's associated Inventory#Slot is henceforth referred to as its slave slot.) The SlotGroup is a group of slots treated as a single logical inventory. It provides the `save` method that serializes the Items (instead of their element) to NBT and loads them afterwards. The InventorySlotGroup extends the SlotGroup by providing synchronization of InventorySlotWidgets with the slave Inventory#Slots. In addition, the Item recovery logic described in the previous paragraph is implemented there — mostly for migration purposes; however, if a de-sync of the kind "the slave slot has an Entity, but the owner thinks it doesn't" occurs (such as when an item is inserted by ocelot-brain for some reason), we also need to recover the Item to give the InventorySlotWidget. Recoverers and item prototypes are registered in the ItemRegistry. Its `recover(E)` method looks up the ItemRecoverer registered for the runtime classes of the provided element and uses it to build an `Item[E]`. The item prototypes, however, are stored in a tree consisting of group nodes whose children are other group nodes and leaves each containing an ItemPrototype. This is used by the item selector to group several entries together. Finally, the SlotWidget's `transient` boolean property indicates whether the slot is in the middle of updating its contents. This is used to ignore the events fired by an Inventory as a result of inserting an item into an InventorySlotWidget. Once the design solidifies, I'll add documentation to the key classes affected by the refactoring to explain the new behavior. --- lib/ocelot-brain | 2 +- .../scala/ocelot/desktop/OcelotDesktop.scala | 80 ++++++-- .../ocelot/desktop/audio/SoundSource.scala | 22 ++- .../ocelot/desktop/geometry/Rect2D.scala | 6 + .../ocelot/desktop/node/NodeRegistry.scala | 5 +- .../desktop/node/nodes/ComputerNode.scala | 173 +++++++----------- .../desktop/node/nodes/DiskDriveNode.scala | 37 ++-- .../ocelot/desktop/node/nodes/TrayNode.scala | 43 ++--- .../desktop/node/nodes/TrayWindow.scala | 2 +- .../scala/ocelot/desktop/ui/UiHandler.scala | 4 +- .../ocelot/desktop/ui/widget/RootWidget.scala | 4 +- .../desktop/ui/widget/WorkspaceView.scala | 12 +- .../inventory/DecoratedSlotWidget.scala | 14 ++ .../widget/inventory/InventorySlotGroup.scala | 154 ++++++++++++++++ .../inventory/InventorySlotWidget.scala | 31 ++++ .../desktop/ui/widget/inventory/Item.scala | 17 ++ .../ui/widget/inventory/ItemPrototype.scala | 33 ++++ .../ui/widget/inventory/ItemRecoverer.scala | 14 ++ .../ui/widget/inventory/ItemRegistry.scala | 60 ++++++ .../inventory/LmbItemSelectionSlot.scala | 30 +++ .../ui/widget/inventory/SlotGroup.scala | 133 ++++++++++++++ .../ui/widget/inventory/SlotWidget.scala | 118 ++++++++++++ .../widget/inventory/TieredSlotWidget.scala | 14 ++ .../inventory/item/CPUItemPrototype.scala | 59 ++++++ .../item/DataCardItemPrototype.scala | 56 ++++++ .../inventory/item/EEPROMItemPrototype.scala | 70 +++++++ .../inventory/item/FloppyItemPrototype.scala | 112 ++++++++++++ .../item/GraphicsCardItemPrototype.scala | 37 ++++ .../inventory/item/HDDItemPrototype.scala | 47 +++++ .../item/InternetCardItemPrototype.scala | 39 ++++ .../item/LinkedCardItemPrototype.scala | 38 ++++ .../inventory/item/MemoryItemPrototype.scala | 47 +++++ .../item/NetworkCardItemPrototype.scala | 34 ++++ .../item/RedstoneCardItemPrototype.scala | 48 +++++ .../WirelessNetworkCardItemPrototype.scala | 45 +++++ .../item/traits/CardItemPrototype.scala | 7 + .../traits/EnvironmentItemPrototype.scala | 21 +++ .../traits/PersistableItemPrototype.scala | 47 +++++ .../item/traits/SlotTieredItemPrototype.scala | 14 ++ .../ui/widget/inventory/slot/AnySlot.scala | 8 + .../ui/widget/inventory/slot/CPUSlot.scala | 49 +++++ .../{ => inventory}/slot/CardRegistry.scala | 2 +- .../ui/widget/inventory/slot/CardSlot.scala | 36 ++++ .../ui/widget/inventory/slot/DiskSlot.scala | 22 +++ .../ui/widget/inventory/slot/EEPROMSlot.scala | 24 +++ .../ui/widget/inventory/slot/FloppySlot.scala | 36 ++++ .../ui/widget/inventory/slot/MemorySlot.scala | 22 +++ .../desktop/ui/widget/slot/CPUSlot.scala | 88 --------- .../desktop/ui/widget/slot/CardSlot.scala | 62 ------- .../desktop/ui/widget/slot/DiskSlot.scala | 34 ---- .../desktop/ui/widget/slot/EEPROMSlot.scala | 28 --- .../desktop/ui/widget/slot/EntitySlot.scala | 9 - .../desktop/ui/widget/slot/FloppySlot.scala | 48 ----- .../ui/widget/slot/InventorySlot.scala | 26 --- .../desktop/ui/widget/slot/MemorySlot.scala | 47 ----- .../desktop/ui/widget/slot/SlotWidget.scala | 75 -------- .../ocelot/desktop/util/TrayInventory.scala | 7 - .../scala/ocelot/desktop/util/package.scala | 8 +- 58 files changed, 1759 insertions(+), 601 deletions(-) create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/DecoratedSlotWidget.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotGroup.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/InventorySlotWidget.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/Item.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/ItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRecoverer.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/ItemRegistry.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/LmbItemSelectionSlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/SlotGroup.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/SlotWidget.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/TieredSlotWidget.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/CPUItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/DataCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/EEPROMItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/FloppyItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/GraphicsCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/HDDItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/InternetCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/LinkedCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/MemoryItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/NetworkCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/RedstoneCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/WirelessNetworkCardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/CardItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/EnvironmentItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/PersistableItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/item/traits/SlotTieredItemPrototype.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/AnySlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CPUSlot.scala rename src/main/scala/ocelot/desktop/ui/widget/{ => inventory}/slot/CardRegistry.scala (98%) create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/CardSlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/DiskSlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/EEPROMSlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/FloppySlot.scala create mode 100644 src/main/scala/ocelot/desktop/ui/widget/inventory/slot/MemorySlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/DiskSlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/EEPROMSlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/EntitySlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/FloppySlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/InventorySlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/MemorySlot.scala delete mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/SlotWidget.scala delete mode 100644 src/main/scala/ocelot/desktop/util/TrayInventory.scala 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) + } }