Refactor inventory system

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.
This commit is contained in:
Fingercomp 2022-07-03 22:37:47 +07:00
parent bfe83a00b3
commit 04f6e647d4
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
58 changed files with 1759 additions and 601 deletions

@ -1 +1 @@
Subproject commit f326a45a0ae223904869ae02ad22ca7afdeb8b1f
Subproject commit 95f04168b920306c066c47308d91b411f95c82a2

View File

@ -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()
})
}
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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))
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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()

View File

@ -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 = {

View File

@ -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()
}

View File

@ -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))
}
}

View File

@ -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
})
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
})
}

View File

@ -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)))
}
}

View File

@ -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)
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -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)))
}

View File

@ -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)))
}

View File

@ -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",
)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 =>
}

View File

@ -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
}))
}
}
}

View File

@ -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
}
}
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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)
// }
}

View File

@ -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}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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])
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -1,7 +0,0 @@
package ocelot.desktop.util
import totoro.ocelot.brain.entity.traits.Inventory
class TrayInventory extends Inventory {
// TODO add something here?
}

View File

@ -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)
}
}