diff --git a/core/src/SlotAction.cpp b/core/src/SlotAction.cpp new file mode 100644 index 0000000..dad767d --- /dev/null +++ b/core/src/SlotAction.cpp @@ -0,0 +1,153 @@ +#include "vf/SlotAction.h" + +#include + +namespace vf { + +namespace { + +// The half of a menu a shift-click aims at. Craft inputs and result slots are +// deliberately not part of it: shift-clicking in a player inventory moves items +// between the rows and the hotbar, it does not stuff the 2x2 grid. +bool isTransferSection(MenuSlot::Role role) { + switch (role) { + case MenuSlot::Role::Storage: + case MenuSlot::Role::FuelInput: + case MenuSlot::Role::SmeltInput: + case MenuSlot::Role::SmeltOutput: + case MenuSlot::Role::BrewBottle: + case MenuSlot::Role::BrewIngredient: + case MenuSlot::Role::EnchantItem: + case MenuSlot::Role::EnchantLapis: + case MenuSlot::Role::AnvilInput: + case MenuSlot::Role::AnvilResult: + case MenuSlot::Role::TradeInput: + case MenuSlot::Role::TradeResult: + return true; + case MenuSlot::Role::Inventory: + case MenuSlot::Role::Hotbar: + case MenuSlot::Role::OffHand: + case MenuSlot::Role::CraftInput: + case MenuSlot::Role::CraftResult: + case MenuSlot::Role::Count: + return false; + } + return false; +} + +std::vector collectRoles(const ContainerMenu& menu, bool wantTransferSection) { + std::vector found; + for (int i = 0; i < menu.slotCount(); ++i) { + if (isTransferSection(menu.slot(i).role) == wantTransferSection) found.push_back(i); + } + return found; +} + +// Where a shift-click from `index` should land. Container half to player half +// and back; in a menu that has no container half, the player's rows and the +// hotbar swap roles instead. +std::vector transferTargetsFor(const ContainerMenu& menu, int index) { + std::vector container = collectRoles(menu, true); + MenuSlot::Role role = menu.slot(index).role; + + if (isTransferSection(role)) return menu.playerSectionSlots(); + if (!container.empty()) return container; + + std::vector targets; + MenuSlot::Role wanted = + role == MenuSlot::Role::Hotbar ? MenuSlot::Role::Inventory : MenuSlot::Role::Hotbar; + for (int i = 0; i < menu.slotCount(); ++i) { + if (menu.slot(i).role == wanted) targets.push_back(i); + } + return targets; +} + +bool isResultSlot(const ContainerMenu& menu, int index) { + return menu.layout().resultSlot >= 0 && index == menu.layout().resultSlot; +} + +void touchCraftInput(ContainerMenu& menu, int index) { + if (menu.slot(index).role == MenuSlot::Role::CraftInput) menu.refreshResult(); +} + +// Every removal from a slot goes through here so the crafting grid can never +// hand out a result without paying for it. +MetaStack takeFromSlot(ContainerMenu& menu, int index, int amount, SlotActionResult& out) { + if (isResultSlot(menu, index)) { + CraftingView view = menu.crafting(); + MetaStack crafted = view.takeResult(); + if (!crafted.empty()) { + out.craftedResult = true; + out.craftedCount += crafted.count; + } + return crafted; + } + MetaStack taken = menu.takeFrom(index, amount); + touchCraftInput(menu, index); + return taken; +} + +int putIntoSlot(ContainerMenu& menu, int index, MetaStack& incoming) { + int moved = menu.insertAt(index, incoming); + if (moved > 0) touchCraftInput(menu, index); + return moved; +} + +bool swapWithSlot(ContainerMenu& menu, int index, CursorStack& cursor) { + MenuSlot& target = menu.slot(index); + if (target.takeOnly) return false; + if (!menu.canPlace(index, cursor.held)) return false; + if (target.cell.limitFor(cursor.held) < cursor.held.count) return false; + cursor.held = target.cell.swapWith(cursor.held); + touchCraftInput(menu, index); + return true; +} + +SlotActionResult leftClick(ContainerMenu& menu, CursorStack& cursor, const SlotAction& action) { + SlotActionResult out; + int index = action.slotIndex; + if (!menu.validIndex(index)) return out; + + if (cursor.empty()) { + MetaStack taken = takeFromSlot(menu, index, menu.stackAt(index).count, out); + if (taken.empty()) return out; + out.moved = taken.count; + cursor.held = taken; + out.cursorChanged = true; + out.applied = true; + return out; + } + + if (isResultSlot(menu, index)) { + const MetaStack& pending = menu.stackAt(index); + if (pending.empty() || !stacksMergeable(cursor.held, pending)) return out; + if (cursor.held.room() < pending.count) return out; + MetaStack crafted = takeFromSlot(menu, index, pending.count, out); + out.moved = crafted.count; + transferAll(crafted, cursor.held); + out.cursorChanged = true; + out.applied = true; + return out; + } + + int moved = putIntoSlot(menu, index, cursor.held); + if (moved > 0) { + out.moved = moved; + out.cursorChanged = true; + out.applied = true; + return out; + } + + if (menu.stackAt(index).empty() || stacksMergeable(menu.stackAt(index), cursor.held)) return out; + if (!swapWithSlot(menu, index, cursor)) return out; + out.moved = cursor.held.count + menu.stackAt(index).count; + out.cursorChanged = true; + out.applied = true; + return out; +} + +SlotActionResult rightClick(ContainerMenu& menu, CursorStack& cursor, const SlotAction& action) { + SlotActionResult out; + int index = action.slotIndex; + if (!menu.validIndex(index)) return out; +