/** * Drag-related helpers for grid cells: set DataTransfer for dragstart, attach drag listeners, handle drop. */ import { tryBuyEgg, tryPlaceEgg, placeMatureBabyOnCell, placeReceptionAnimalOnCell } from "./zoo.js"; import { moveCell } from "./placement.js"; import { t, errorMessage } from "./texts-fr.js"; /** * @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, lastActionWasDrop: { current: boolean } }} ctx * @param {DragEvent} e * @param {{ x: number, y: number, empty: boolean }} target * @returns {boolean} */ export function handleCellDropNurseryReceptionToken(ctx, e, target) { const { state, setState, setError, playSound } = ctx; const { x: toX, y: toY, empty } = target; const nurseryCellKey = e.dataTransfer.getData("application/x-builazoo-nursery-cell-key"); if (nurseryCellKey && empty) { const nowUnix = Math.floor(Date.now() / 1000); const [ok, reason] = placeMatureBabyOnCell(state, { nurseryCellKey, toX, toY, nowUnix }); if (ok) { setError(""); playSound("place"); } else { setError(String(t.errorPrefix).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); } ctx.lastActionWasDrop.current = true; setState(); return true; } const receptionCellKey = e.dataTransfer.getData("application/x-builazoo-reception-cell-key"); if (receptionCellKey && empty) { const nowUnix = Math.floor(Date.now() / 1000); const [ok, reason] = placeReceptionAnimalOnCell(state, { receptionCellKey, toX, toY, nowUnix }); if (ok) { setError(""); playSound("place"); } else { setError(String(t.errorPrefix).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); } ctx.lastActionWasDrop.current = true; setState(); return true; } const tokenIdStr = e.dataTransfer.getData("application/x-builazoo-tokenid"); if (tokenIdStr && empty) { const tokenId = Number(tokenIdStr); if (!Number.isNaN(tokenId)) { const nowUnix = Math.floor(Date.now() / 1000); const [placeOk, placeReason] = tryPlaceEgg(state, { tokenId, x: toX, y: toY, nowUnix }); if (placeOk) { setError(""); playSound("place"); } else { setError(String(t.errorPrefix).replace("%s", errorMessage[placeReason] ?? placeReason)); playSound("error"); } ctx.lastActionWasDrop.current = true; setState(); } return true; } return false; } /** * @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, lastActionWasDrop: { current: boolean } }} ctx * @param {DragEvent} e * @param {{ x: number, y: number, empty: boolean }} target * @returns {boolean} */ export function handleCellDropEggTypeOrMove(ctx, e, target) { const { state, setState, setError, playSound } = ctx; const { x: toX, y: toY, empty } = target; const eggTypeFromConveyor = e.dataTransfer.getData("application/x-builazoo-eggtype"); if (eggTypeFromConveyor && empty) { const [buyOk, buyResult] = tryBuyEgg(state, eggTypeFromConveyor); if (!buyOk) { setError(String(t.buyFailed).replace("%s", errorMessage[buyResult] ?? buyResult)); playSound("error"); } else { const tokenId = buyResult.tokenId; const nowUnix = Math.floor(Date.now() / 1000); const [placeOk, placeReason] = tryPlaceEgg(state, { tokenId, x: toX, y: toY, nowUnix }); if (placeOk) { setError(""); playSound("place"); } else { setError(String(t.errorPrefix).replace("%s", errorMessage[placeReason] ?? placeReason)); playSound("error"); } } ctx.lastActionWasDrop.current = true; setState(); return true; } const raw = e.dataTransfer.getData("text/plain"); if (!raw || !/^\d+_\d+$/.test(raw)) return true; const [sx, sy] = raw.split("_").map(Number); const [ok, reason] = moveCell(state, { fromX: sx, fromY: sy, toX, toY }); if (!ok) setError(String(t.errorPrefix).replace("%s", errorMessage[reason] ?? reason)); else setError(""); ctx.lastActionWasDrop.current = true; setState(); return true; } /** * @param {HTMLElement} div * @param {import("./types.js").GridCell | null | undefined} cell * @param {{ x: number, y: number }} pos * @param {DataTransfer} dt */ export function setCellDragData(div, cell, pos, dt) { const { x, y } = pos; let dragX = x; let dragY = y; if (div.dataset.nurseryCellKey) { dt.setData("application/x-builazoo-nursery-cell-key", div.dataset.nurseryCellKey); dt.effectAllowed = "move"; } else if (div.dataset.receptionCellKey) { dt.setData("application/x-builazoo-reception-cell-key", div.dataset.receptionCellKey); dt.effectAllowed = "move"; } else if (cell && cell.kind === "animal" && cell.originKey !== null && cell.originKey !== undefined) { const m = cell.originKey.match(/^(\d+)_(\d+)$/); if (m) { dragX = Number(m[1]); dragY = Number(m[2]); } } if (!div.dataset.nurseryCellKey && !div.dataset.receptionCellKey) dt.setData("text/plain", `${dragX}_${dragY}`); if (cell && cell.kind === "nursery" && cell.tokenId !== null && cell.tokenId !== undefined) { dt.setData("application/x-builazoo-tokenid", String(cell.tokenId)); } dt.effectAllowed = dt.effectAllowed || "move"; } /** * @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, gridEl: HTMLElement }} ctx * @param {{ div: HTMLElement, cell: import("./types.js").GridCell | null | undefined, x: number, y: number, key: string }} opts */ export function attachDragListeners(ctx, opts) { const { div, cell } = opts; const { gridEl } = ctx; const isDraggable = cell !== null && cell !== undefined && ( cell.kind === "egg" || cell.kind === "animal" || (cell.kind === "nursery" && (cell.tokenId !== null && cell.tokenId !== undefined || div.dataset.nurseryCellKey)) || (cell.kind === "reception" && div.dataset.receptionCellKey) ); if (!isDraggable) return; div.addEventListener("dragstart", (e) => { setCellDragData(div, cell, { x: opts.x, y: opts.y }, e.dataTransfer); div.classList.add("dragging"); const ghost = div.cloneNode(true); ghost.classList.add("drag-ghost"); ghost.style.opacity = "1"; document.body.appendChild(ghost); e.dataTransfer.setDragImage(ghost, 24, 24); div.addEventListener("dragend", () => { ghost.remove(); }, { once: true }); }); div.addEventListener("dragend", () => { div.classList.remove("dragging"); gridEl.querySelectorAll(".cell").forEach((c) => c.classList.remove("dragover")); }); }