diff --git a/core/include/vf/SlotAction.h b/core/include/vf/SlotAction.h new file mode 100644 index 0000000..7d59a54 --- /dev/null +++ b/core/include/vf/SlotAction.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include "ContainerMenu.h" + +namespace vf { + +// Click semantics for an open ContainerMenu. This is where inventories usually +// break, so the whole vocabulary lives in one place and every action reports +// exactly what it moved. + +enum class ClickKind : uint8_t { + Left, // take all / put all / swap + Right, // take half / put one + ShiftLeft, // move to the other section, merging before filling + ShiftRight, // same rule; kept distinct so callers can bind both + HotbarSwap, // number key: exchange the slot with a hotbar slot + Middle, // creative clone, otherwise nothing happens + Double, // gather every mergeable stack onto the cursor + DragLeftStart, + DragLeftAdd, + DragLeftEnd, // split the held stack evenly, remainder stays held + DragRightStart, + DragRightAdd, + DragRightEnd, // one item per dragged slot + DropOne, // Q + DropStack, // Ctrl+Q + Count +}; + +const char* clickKindName(ClickKind kind); + +// The stack the mouse carries between slots. It is deliberately its own type: +// the cursor is not a menu slot and must never be reachable by index. +struct CursorStack { + MetaStack held; + + bool empty() const { return held.empty(); } + void clear() { held.clear(); } +}; + +// A drag paints a set of slots while the button is down. The items stay on the +// cursor for the whole gesture and are only distributed on release, so an +// abandoned drag cannot lose anything. +struct DragState { + bool active = false; + bool rightButton = false; + std::vector paintedSlots; + // Cursor contents when the drag began, needed to compute an even split. + MetaStack payload; + + void reset(); + bool alreadyPainted(int index) const; +}; + +struct SlotAction { + ClickKind kind = ClickKind::Left; + int slotIndex = -1; + // Which hotbar slot a HotbarSwap targets, 0..8. + int hotbarButton = -1; +}; + +SlotAction clickAt(ClickKind kind, int slotIndex); +SlotAction hotbarSwapAt(int slotIndex, int hotbarButton); + +struct SlotActionResult { + bool applied = false; + // Items that changed slot, or moved between a slot and the cursor. + int moved = 0; + // Items that left the menu entirely. They are also listed in droppedStacks + // so a caller can spawn the right entities. + int dropped = 0; + std::vector droppedStacks; + // Only a creative middle click can be non-zero here. Every other action is + // conservative, which is what the invariant test pins down. + int created = 0; + bool cursorChanged = false; + bool craftedResult = false; + int craftedCount = 0; + + void noteDrop(const MetaStack& stack); +}; + +// Applies one click. `cursor` and `drag` are the player's own state and are +// carried between calls; the menu is the screen being clicked on. +SlotActionResult applySlotAction(ContainerMenu& menu, CursorStack& cursor, DragState& drag, + const SlotAction& action); + +// Total items of one kind across everything the action model can touch: the +// menu, the cursor, and whatever a drag is holding. The invariant is that this +// number only ever changes by what an action reports as dropped or created. +int totalAcrossMenuAndCursor(const ContainerMenu& menu, const CursorStack& cursor, ItemId item); +int totalItemsAcrossMenuAndCursor(const ContainerMenu& menu, const CursorStack& cursor); + +} // namespace vf