161 lines
4.8 KiB
C++
161 lines
4.8 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#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;
|
|
|