diff --git a/core/include/vf/ContainerMenu.h b/core/include/vf/ContainerMenu.h new file mode 100644 index 0000000..d047bcf --- /dev/null +++ b/core/include/vf/ContainerMenu.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include + +#include "Container.h" +#include "Crafting.h" +#include "ItemStack.h" + +namespace vf { + +// A menu is the screen an open container puts in front of the player. It does +// not replace Container: a Container is still the storage that lives in the +// world and that hoppers, droppers and comparators read. The menu binds one for +// the duration of the screen, adds the player's own sections next to it, and +// gives every cell a role and an admission rule. + +// What a slot is willing to accept. The result of a craft, a smelt or a trade +// takes Nothing, which is what makes it take-only. +struct SlotFilter { + enum class Kind : uint8_t { + Any, + Nothing, + Fuel, // furnace fuel, per Crafting.h isFuel + Smeltable, // has a smelting recipe in the bound registry + Lapis, // enchanting table payment + Enchantable, // enchantabilityOf > 0 + Repairable, // anvil input: tools carry durability, so they repair + BrewIngredient, // the brewing ingredients that exist as items here + Count + }; + + Kind kind = Kind::Any; + + // `registry` is only consulted by Smeltable; every other kind answers from + // the item tables alone and accepts a null registry. + bool accepts(const MetaStack& stack, const CraftingRegistry* registry) const; +}; + +const char* slotFilterKindName(SlotFilter::Kind kind); + +// One storage cell: a stack plus a cap. `capacity` of zero defers to the item's +// own limit, anything else clamps below it (a brewing bottle holds exactly one). +struct StackSlot { + MetaStack stack; + int capacity = 0; + + bool empty() const { return stack.empty(); } + int limitFor(const MetaStack& candidate) const; + int room(const MetaStack& candidate) const; + + // Merges what fits from `incoming`, leaving the rest in it. Returns moved. + int insert(MetaStack& incoming); + MetaStack take(int amount); + MetaStack takeAll(); + MetaStack swapWith(const MetaStack& incoming); +}; + +enum class MenuKind : uint8_t { + PlayerInventory, + CraftingTable, + Chest, + LargeChest, + Furnace, + BrewingStand, + EnchantingTable, + Anvil, + Villager, + Hopper, + Count +}; + +const char* menuKindName(MenuKind kind); + +struct MenuSlot { + // Which part of the screen the slot belongs to. Shift-clicking moves items + // between the container half and the player half, so the roles have to be + // distinguishable at runtime, not just visually. + enum class Role : uint8_t { + Storage, // the bound container, or the menu's own storage + Inventory, // the player's three rows + Hotbar, // the player's bottom row + CraftInput, + CraftResult, + FuelInput, + SmeltInput, + SmeltOutput, + BrewBottle, + BrewIngredient, + EnchantItem, + EnchantLapis, + AnvilInput, + AnvilResult, + TradeInput, + TradeResult, + OffHand, + Count + }; + + StackSlot cell; + Role role = Role::Storage; + SlotFilter filter; + // A take-only slot never accepts a placement, however empty it is. + bool takeOnly = false; + // Index into the bound Container, or -1 when the menu owns the stack. + int backingIndex = -1; +}; + +const char* menuSlotRoleName(MenuSlot::Role role); + +// Where each section starts and how long it is, so callers can iterate a +// section without knowing the slot order by heart. +struct MenuLayout { + MenuKind kind = MenuKind::Chest; + + int storageFirst = -1; + int storageCount = 0; + int inventoryFirst = -1; + int inventoryCount = 0; + int hotbarFirst = -1; + int hotbarCount = 0; + int craftFirst = -1; + int craftWidth = 0; + int craftHeight = 0; + int resultSlot = -1; + int offHandSlot = -1; + + int totalSlots = 0; + + int craftCells() const { return craftWidth * craftHeight; } + bool hasCrafting() const { return craftFirst >= 0 && craftCells() > 0; } +}; + +MenuLayout layoutFor(MenuKind kind); + +constexpr int playerInventoryRows = 3; +constexpr int playerInventoryColumns = 9; +constexpr int playerInventorySlots = playerInventoryRows * playerInventoryColumns; +constexpr int playerHotbarSlots = 9; + +class ContainerMenu; + +// The crafting half of a menu, wired to the existing CraftingRegistry. It owns +// no stacks and no lifetime: it reads and writes the menu's craft slots in +// place, so the grid the player sees and the grid the recipe matcher sees are +// the same cells. A menu without a crafting section hands out a view whose +// cellCount is zero and whose operations therefore do nothing. +class CraftingView { +public: + explicit CraftingView(ContainerMenu& menu); + + int width() const; + int height() const; + int cellCount() const; + + MetaStack& input(int x, int y); + const MetaStack& input(int x, int y) const; + MetaStack& inputAt(int cell); + const MetaStack& inputAt(int cell) const; +