Ralith / hypermine

A hyperbolic voxel game
Apache License 2.0
159 stars 19 forks source link

Add an inventory system #397

Open patowen opened 4 months ago

patowen commented 4 months ago

As a first step to adding actual gameplay, the player should be able to obtain, organize, and use resources, so to allow that, we will need to give the player an inventory. Implementing this is somewhat involved, as it has several moving parts:

To avoid needing to implement "item entities" or an equivalent feature, breaking blocks should immediately put them in the player's inventory. This might also be a good quality-of-life feature in general to keep long-term. However, for now, if the player's inventory is full, the fallback should be the simplest to implement: The block should be erased from existence.

It shouldn't matter exactly what the inventory controls are or how certain selections are made, as long as the code is written in such a way that tweaks are feasible. Just having this feature exist at all should be the priority.

Ralith commented 4 months ago

To start with, we must define a data model. A simple and flexible in-memory data model is a plain Vec<Entity> sitting in a discrete component. This interacts with the described goals as follows:

A GUI for the inventory that allows a player to move items around

Left to future work. Future work could annotate the Vec with positions in a grid. Alternatively, many games have done just fine with plain lists of items.

A way to select materials to place

We already have a material selection scheme, albeit a primitive one. It could be easily extended to scan the inventory to find an Entity that represents a block of the selected material and consume it. This might seem inefficient, but it's implausible that inventories will ever become large enough for this to matter, and we can always add an index later if needed. A more usable GUI can be developed in follow-up work, either as an extension of the main inventory GUI or a dedicated interface.

A server-authoritative inventory

common::proto can be extended with an event for use by the server to communicate the addition or removal of an entity to/from another entity's inventory, to be included in Spawns as with all other discrete events. For the client's part, it is sufficient to extend BlockUpdate to specify the EntityId to be consumed when placing a block.

Serialization of the player inventory for saving

As a component, inventory should be encoded into a column of the bytes field representing the entity that owns it (for now, always a character) in its EntityNode. A plain [EntityId] encoding will suffice.

The stored block entities themselves are a bit of a funny case, since they don't necessarily have a location in world space. For serialization purposes, we might as well deem them part of the same EntityNode as the entity that owns the inventory, which ensures they are loaded when relevant.

Atomic modifications to the save file for placing and breaking blocks (to avoid item loss or duplication)

Thanks to redb, all save file updates are transactional, so this requires no effort beyond choosing not to gratuitously split the transaction.

Ralith commented 4 months ago

The choice to represent resources as first-class entities may seem strange, but is deliberate. It is certain that we'll want to store things other than raw materials, and those will have additional state (i.e. components) attached. By embracing this model from the start, we can reduce the number of special cases, without any real drawbacks.

patowen commented 4 months ago

The choice to represent resources as first-class entities may seem strange, but is deliberate. It is certain that we'll want to store things other than raw materials, and those will have additional state (i.e. components) attached. By embracing this model from the start, we can reduce the number of special cases, without any real drawbacks.

That makes sense to me. The only potential downside I can think of is efficiency, but to fix that, for simple materials, if we needed to in the future, we could always if we needed to add the optimization of the ItemStack component that could just have a material ID and a number. Use of entities here will give us the flexibility we need, so I'm optimistic about this as the foundational design for inventory.