summaryrefslogtreecommitdiff
path: root/2026/games/the0x539/interaction.js
diff options
context:
space:
mode:
Diffstat (limited to '2026/games/the0x539/interaction.js')
-rw-r--r--2026/games/the0x539/interaction.js581
1 files changed, 0 insertions, 581 deletions
diff --git a/2026/games/the0x539/interaction.js b/2026/games/the0x539/interaction.js
deleted file mode 100644
index d45d5c9..0000000
--- a/2026/games/the0x539/interaction.js
+++ /dev/null
@@ -1,581 +0,0 @@
-import { stackSizes } from './data.js';
-import { getRecipeOutput } from './crafting.js';
-
-class CraftEvent extends Event {
- item;
- constructor(item) {
- super('craft');
- this.item = item;
- }
-}
-
-const grabbedStack = document.querySelector('grabbed-stack');
-const craftingGrid = document.querySelector('crafting-grid');
-const inventoryGrid = document.querySelector('inventory-grid');
-const outputCell = document.querySelector('crafting-output');
-
-let state = 'idle';
-
-const splitTargets = new Set();
-
-export function resetInventory() {
- for (const stack of document.querySelectorAll('item-stack')) {
- stack.remove();
- }
- splitTargets.clear();
- state = 'idle';
-}
-
-export function createItem(item, count) {
- const elem = document.createElement('item-stack');
- elem.setAttribute('data-item', item);
- elem.setAttribute('data-count', count);
- const countElem = document.createElement('data');
- countElem.textContent = count.toString();
- elem.appendChild(countElem);
- return elem;
-}
-
-export function mouseDown(event) {
- if (state !== 'idle') return;
- if (event.button !== 0 && event.button !== 2) return;
-
- const cell = event.currentTarget;
-
- // Shift click: Transfer a full stack to the other place.
- if (event.shiftKey) {
- const sourceStack = cell.firstElementChild;
- if (sourceStack) {
- const targetGrid = cell.parentElement === inventoryGrid ? craftingGrid : inventoryGrid;
- shiftClickTransfer(sourceStack, targetGrid);
- }
- state = 'shift-drag';
- return;
- }
-
- const heldStack = grabbedStack.firstElementChild;
-
- // Click with empty cursor: pick up items
- if (!heldStack) {
- const sourceStack = cell.firstElementChild;
- if (!sourceStack) {
- return;
- }
-
- if (event.button === 2) {
- // Right click: take half the stack, rounded up
- const sourceCount = getCount(sourceStack);
- if (sourceCount === 1) {
- grabbedStack.appendChild(sourceStack);
- updateRecipeOutput();
- return;
- }
-
- const takenCount = Math.ceil(sourceCount / 2);
- const newCount = sourceCount - takenCount;
- const item = sourceStack.getAttribute('data-item');
- setCount(sourceStack, newCount);
- grabbedStack.appendChild(createItem(item, takenCount));
- } else {
- // Left click: take the whole stack. Drag to pick up other stacks
- grabbedStack.appendChild(sourceStack);
- state = 'pickup-drag';
- updateRecipeOutput();
- }
- } else {
- // Click with items held: deposit items
-
- if (event.button === 2) {
- // Right click: deposit one item. Drag to deposit one item in each cell
- state = 'split-one';
- depositOne(cell);
- } else {
- // Left click: split stack evenly across all empty/compatible cells.
- // If no compatible cells are dragged over, swap stack with the cell the mouse was released on.
- // (In the common case, this is just what a "click" looks like on a cell with incompatible contents.)
- heldStack.setAttribute('data-original-count', heldStack.getAttribute('data-count'));
- state = 'split-evenly';
- addSplitTarget(cell);
- }
- }
-}
-
-export function mouseEnter(event) {
- const cell = event.currentTarget;
-
- switch (state) {
- case 'shift-drag': {
- const sourceStack = cell.firstElementChild;
- if (!sourceStack) break;
- const targetGrid = cell.parentElement === inventoryGrid ? craftingGrid : inventoryGrid;
- shiftClickTransfer(sourceStack, targetGrid);
- updateRecipeOutput();
- break;
- }
-
- case 'pickup-drag': {
- const sourceStack = cell.firstElementChild;
- if (!sourceStack) break;
- const heldStack = grabbedStack.firstElementChild;
- const item = sourceStack.getAttribute('data-item');
- if (item !== heldStack.getAttribute('data-item')) return;
-
- const stackSize = stackSizes[item] ?? 64;
-
- const newCount = getCount(heldStack) + getCount(sourceStack);
- if (newCount > stackSize) return;
-
- setCount(heldStack, newCount);
- sourceStack.remove();
- updateRecipeOutput();
- break;
- }
-
- case 'split-one':
- depositOne(cell);
- break;
-
- case 'split-evenly':
- addSplitTarget(cell);
- break;
-
- default:
- break;
- }
-}
-
-export function mouseUp(event) {
- switch (state) {
- case 'split-evenly':
- case 'split-exhausted': {
- if (event.button !== 0) return;
-
- if (splitTargets.size <= 1) {
- const targetCell = event.target.closest('inventory-cell');
- if (targetCell) {
- depositAll(targetCell);
- }
- } else {
- commitSplit();
- }
- break;
- }
-
- case 'pickup-drag':
- if (event.button !== 0) return;
- state = 'idle';
- break;
-
- case 'split-one':
- if (event.button !== 2) return;
- state = 'idle';
- break;
-
- case 'shift-drag':
- // this could be either button. whatever.
- state = 'idle';
- break;
-
- default:
- break;
- }
-}
-
-export function craftClick(event) {
- if (event.shiftKey) {
- craftAll();
- } else {
- craftOne();
- }
-}
-
-function craftOne() {
- const previewStack = outputCell.firstElementChild;
- if (!previewStack) return;
-
- const item = previewStack.getAttribute('data-item');
- const craftCount = getCount(previewStack);
- const stackSize = stackSizes[item] ?? 64;
-
- const heldStack = grabbedStack.firstElementChild;
- if (heldStack !== null) {
- if (heldStack.getAttribute('data-item') !== item) {
- return;
- }
-
- const heldCount = getCount(heldStack);
- if (heldCount + craftCount > stackSize) {
- return;
- }
-
- setCount(heldStack, heldCount + craftCount);
- } else {
- grabbedStack.appendChild(createItem(item, craftCount));
- }
- consumeIngredients();
- document.dispatchEvent(new CraftEvent(item));
-}
-
-function craftAll() {
- const previewStack = outputCell.firstElementChild;
- if (!previewStack) return;
-
- const item = previewStack.getAttribute('data-item');
- const craftCount = getCount(previewStack);
- const stackSize = stackSizes[item] ?? 64;
-
- let success = false;
-
- do {
- let unallocated = craftCount;
-
- const nonEmptyTargets = [];
- let emptyTarget = null;
-
- for (const cell of inventoryGrid.children) {
- const stack = cell.firstElementChild;
- if (stack === null) {
- // This slot is empty.
- // All crafting outputs that don't fit into existing stacks
- // will be deposited into the first empty slot.
- emptyTarget ??= cell;
- continue;
- }
-
- if (stack.getAttribute('data-item') !== item) {
- // This slot already contains a different item,
- // so we can't add crafting outputs to it.
- continue;
- }
-
- const spareCapacity = stackSize - getCount(stack);
- if (spareCapacity <= 0) {
- // This slot contains the correct item, but is already full.
- continue;
- }
-
- const amountToAdd = Math.min(unallocated, spareCapacity);
- unallocated -= amountToAdd;
- nonEmptyTargets.push([stack, amountToAdd]);
-
- if (unallocated === 0) {
- // All crafting outputs can fit into existing stacks of the item.
- break;
- }
- }
-
- if (unallocated > 0 && emptyTarget === null) {
- // The inventory is full: no slots are empty and existing stacks don't have enough room.
- // Abort this attempt and conclude the loop.
- break;
- }
-
- // Add crafting output to existing stacks, to whatever extent possible.
- for (const [stack, amountToAdd] of nonEmptyTargets) {
- setCount(stack, getCount(stack) + amountToAdd);
- }
-
- // Put any remaining items into the first empty slot.
- if (unallocated > 0) {
- emptyTarget.appendChild(createItem(item, unallocated));
- }
-
- success = true;
-
- // Consume one set of ingredients.
- // Cease crafting if this causes the recipe output to change.
- } while (!consumeIngredients());
-
- if (success) {
- document.dispatchEvent(new CraftEvent(item));
- }
-}
-
-function consumeIngredients() {
- let anyExhausted = false;
-
- for (const cell of craftingGrid.children) {
- const stack = cell.firstElementChild;
- if (!stack) continue;
-
- const count = getCount(stack);
- if (count === 1) {
- stack.remove();
- anyExhausted = true;
- } else {
- setCount(stack, count - 1);
- }
- }
-
- return anyExhausted && updateRecipeOutput();
-}
-
-// Returns whether the output actually changed
-function updateRecipeOutput() {
- const outputStack = outputCell.firstElementChild;
-
- const output = getRecipeOutput();
- if (output === null) {
- if (outputStack === null) {
- return false;
- } else {
- outputStack.remove();
- return true;
- }
- }
-
- const [item, count] = output;
- if (outputStack === null) {
- outputCell.appendChild(createItem(item, count));
- return true;
- }
-
- if (outputStack.getAttribute('data-item') === item && getCount(outputStack) === count) {
- return false;
- }
-
- outputStack.setAttribute('data-item', item);
- setCount(outputStack, count);
- return true;
-}
-
-export function consumeClock() {
- if (state === 'split-exhausted') {
- commitSplit();
- }
-
- if (state === 'split-evenly' && splitTargets.size > 1) {
- // oh dear. this is a complicated situation
- // first, let's try to search for uninvolved clocks
- const uninvolved = document.querySelector('inventory-cell > item-stack[data-item="clock"]:not([data-original-count]):not(crafting-output item-stack)');
- if (!!uninvolved) {
- const count = getCount(uninvolved);
- if (count === 1) {
- uninvolved.remove();
- } else {
- setCount(uninvolved, count - 1);
- }
- // phew
- return;
- }
-
- // okay so we will actually need to remove a clock from the items being split, ugh
- // not a lot of code, but low confidence that it works properly
- const heldStack = grabbedStack.firstElementChild;
- const count = +heldStack.getAttribute('data-original-count');
- heldStack.setAttribute('data-original-count', count - 1);
- updateSplitPreview();
- return;
- }
-
- const stack = document.querySelector('item-stack[data-item="clock"][data-count]:not(crafting-output item-stack)')
- const count = getCount(stack);
- if (count === 1) {
- if (stack.parentElement === grabbedStack) {
- // conclude drag operations if they're done using a stack of clocks that gets deleted
- state = 'idle';
- }
- stack.remove();
- } else {
- setCount(stack, count - 1);
- }
-}
-
-function getCount(itemStack) {
- return +itemStack.getAttribute('data-count');
-}
-
-function setCount(itemStack, value) {
- itemStack.setAttribute('data-count', value);
- itemStack.firstElementChild.textContent = value.toString();
-}
-
-function depositOne(targetCell) {
- const heldStack = grabbedStack.firstElementChild;
- const item = heldStack.getAttribute('data-item');
- const heldCount = getCount(heldStack);
-
- const stackSize = stackSizes[item] ?? 64;
-
- const targetStack = targetCell.firstElementChild;
- if (targetStack) {
- if (targetStack.getAttribute('data-item') !== item) {
- return;
- }
-
- const targetCount = getCount(targetStack);
- if (targetCount >= stackSize) return;
-
- setCount(targetStack, targetCount + 1);
- } else {
- targetCell.appendChild(createItem(item, 1));
- }
-
- if (heldCount === 1) {
- heldStack.remove();
- state = 'idle';
- } else {
- setCount(heldStack, heldCount - 1);
- }
-
- updateRecipeOutput();
-}
-
-function shiftClickTransfer(sourceStack, targetGrid) {
- const item = sourceStack.getAttribute('data-item');
- const stackSize = stackSizes[item] ?? 64;
- let count = getCount(sourceStack);
-
- // First, search for existing stacks to add to
- for (const cell of targetGrid.children) {
- const targetStack = cell.firstElementChild;
- if (!targetStack) continue;
- if (targetStack.getAttribute('data-item') !== item) continue;
- const curCount = getCount(targetStack);
- const available = stackSize - curCount;
- if (available === 0) continue;
-
- const transferSize = Math.min(count, available);
- const newCount = curCount + transferSize;
- setCount(targetStack, newCount);
-
- count -= transferSize;
- if (count === 0) {
- sourceStack.remove();
- updateRecipeOutput();
- return;
- } else {
- setCount(sourceStack, count);
- }
- }
-
- // Now, look for any empty slots to move whatever's left to
- for (const cell of targetGrid.children) {
- if (!cell.firstElementChild) {
- cell.appendChild(sourceStack);
- break;
- }
- }
-
- updateRecipeOutput();
-}
-
-function addSplitTarget(targetCell) {
- const heldStack = grabbedStack.firstElementChild;
- const item = heldStack.getAttribute('data-item');
-
- const targetStack = targetCell.firstElementChild;
- if (targetStack) {
- if (targetStack.getAttribute('data-item') !== item) {
- return;
- }
- }
-
- splitTargets.add(targetCell);
-
- if (splitTargets.size > 1) {
- updateSplitPreview();
- }
-}
-
-function updateSplitPreview() {
- const heldStack = grabbedStack.firstElementChild;
- const item = heldStack.getAttribute('data-item');
- const heldCount = +heldStack.getAttribute('data-original-count');
-
- const stackSize = stackSizes[item] ?? 64;
-
- const splitCount = Math.max(1, Math.floor(heldCount / splitTargets.size));
- let remainder = heldCount;
-
- for (const targetCell of splitTargets) {
- let targetStack = targetCell.firstElementChild;
- if (!targetStack) {
- targetStack = createItem(item, 0);
- targetCell.appendChild(targetStack);
- }
- if (!targetStack.hasAttribute('data-original-count')) {
- targetStack.setAttribute('data-original-count', targetStack.getAttribute('data-count'));
- }
- const targetCount = +targetStack.getAttribute('data-original-count');
- const available = stackSize - targetCount;
- const transferSize = Math.min(splitCount, available);
- setCount(targetStack, targetCount + transferSize);
- remainder -= transferSize;
- // TODO: give stacks yellow text if they're too full to accomodate their full share of the split
-
- if (remainder === 0) break;
- }
-
- setCount(heldStack, remainder);
-
- if (splitCount === 1 && remainder === 0) {
- state = 'split-exhausted';
- }
-}
-
-function commitSplit() {
- const heldStack = grabbedStack.firstElementChild;
- const heldCount = getCount(heldStack);
-
- if (heldCount === 0) {
- heldStack.remove();
- } else {
- heldStack.removeAttribute('data-original-count');
- }
-
- for (const target of splitTargets) {
- target.firstElementChild?.removeAttribute('data-original-count');
- }
-
- splitTargets.clear();
-
- state = 'idle';
-
- updateRecipeOutput();
-}
-
-function depositAll(targetCell) {
- const heldStack = grabbedStack.firstElementChild;
- const item = heldStack.getAttribute('data-item');
- const heldCount = getCount(heldStack);
-
- const stackSize = stackSizes[item] ?? 64;
-
- const targetStack = targetCell.firstElementChild;
- if (!!targetStack && targetStack.getAttribute('data-item') !== item) {
- // swap held and target stacks because they don't match
- targetCell.appendChild(heldStack);
- grabbedStack.appendChild(targetStack);
- } else {
- if (!targetStack) {
- // deposit the full stack to the slot
- targetCell.appendChild(heldStack);
- } else {
- // deposit as much as possible
- const targetCount = getCount(targetStack);
- const available = stackSize - targetCount;
- const transferSize = Math.min(heldCount, available);
- const newCount = targetCount + transferSize;
- setCount(targetStack, newCount);
-
- if (transferSize < heldCount) {
- setCount(heldStack, heldCount - transferSize);
- } else {
- heldStack.remove();
- }
- }
- }
-
- heldStack.removeAttribute('data-original-count');
-
- for (const target of splitTargets) {
- target.firstElementChild?.removeAttribute('data-original-count');
- }
-
- splitTargets.clear();
-
- state = 'idle';
-
- updateRecipeOutput();
-}