Fingercomp 04f6e647d4
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.
2022-07-03 22:37:47 +07:00
..
2022-07-03 22:37:47 +07:00