**Motivations:** - Ensure lint config is not degraded and fix all lint errors for pousse workflow. **Root causes:** - Unused variables kept with _ prefix instead of removed (_row, _questReward, _i). - getAnimalBlockOrigin had 5 parameters (max 4). - use of continue statement (no-continue rule). **Correctifs:** - ESLint config verified; no eslint-disable in codebase. - Removed unused variable _row (biome-rules); removed dead function _questReward (quests); removed unused map param _i (state.js). - getAnimalBlockOrigin refactored to 4 params (pos object instead of x, y). - Replaced continue with if (cell) block in normalizeLoadedCells (state.js). - JSDoc param names aligned with _height, _y (biome-rules). **Evolutions:** - (none) **Pages affectées:** - web/js/biome-rules.js - web/js/quests.js - web/js/state.js - web/js/placement.js
214 lines
8.4 KiB
JavaScript
214 lines
8.4 KiB
JavaScript
import { tryBuyLabEgg } from "./zoo.js";
|
|
import { GameConfig } from "./config.js";
|
|
import { eggTypeLabel, errorMessage, t } from "./texts-fr.js";
|
|
|
|
const EGG_EMOJI = "🥚";
|
|
|
|
/**
|
|
* @param {{ state: import("./types.js").GameState, setError: (s: string) => void, playSound: (s: string) => void, setState: () => void, pendingTokenByEggType: Record<string, number> }} ctx
|
|
* @param {{ eggType: string }} labOffer
|
|
* @param {boolean} ok
|
|
* @param {{ tokenId?: number } | string} result
|
|
*/
|
|
function handleLabOfferClick(ctx, labOffer, ok, result) {
|
|
if (!ok) {
|
|
const msg = errorMessage[result] ?? result;
|
|
ctx.setError(String(t.buyFailed).replace("%s", msg));
|
|
ctx.playSound("error");
|
|
ctx.setState();
|
|
return;
|
|
}
|
|
ctx.setError("");
|
|
ctx.playSound("buy");
|
|
ctx.pendingTokenByEggType[labOffer.eggType] = result.tokenId;
|
|
ctx.setState();
|
|
}
|
|
|
|
/**
|
|
* @param {{ eggType: string, price: number }} labOffer
|
|
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, pendingTokenByEggType: Record<string, number> }} ctx
|
|
* @returns {HTMLElement}
|
|
*/
|
|
function createLabOfferButton(labOffer, ctx) {
|
|
const { state } = ctx;
|
|
const el = document.createElement("div");
|
|
el.className = "offer-btn world-map-offer world-map-offer-single world-map-lab-offer";
|
|
el.setAttribute("role", "button");
|
|
el.setAttribute("tabindex", "0");
|
|
el.setAttribute("draggable", "true");
|
|
const name = eggTypeLabel[labOffer.eggType] ?? labOffer.eggType;
|
|
el.innerHTML = `<span class="offer-emoji">${EGG_EMOJI}</span><span class="offer-label">${name}</span><span class="offer-price">${labOffer.price} pièces</span>`;
|
|
let dragStarted = false;
|
|
el.addEventListener("dragstart", (e) => {
|
|
dragStarted = true;
|
|
e.dataTransfer.setData("application/x-builazoo-eggtype", labOffer.eggType);
|
|
e.dataTransfer.effectAllowed = "copy";
|
|
el.classList.add("dragging");
|
|
});
|
|
el.addEventListener("dragend", () => { dragStarted = false; el.classList.remove("dragging"); });
|
|
el.addEventListener("click", () => {
|
|
if (dragStarted) return;
|
|
const [ok, result] = tryBuyLabEgg(state, labOffer.eggType);
|
|
handleLabOfferClick(ctx, labOffer, ok, result);
|
|
});
|
|
el.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); el.click(); }
|
|
});
|
|
return el;
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapEl: HTMLElement, state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, pendingTokenByEggType: Record<string, number> }} ctx
|
|
*/
|
|
export function renderLab(ctx) {
|
|
const lab = GameConfig.WorldMap?.Laboratory;
|
|
if (!lab) return;
|
|
const { state, worldMapEl } = ctx;
|
|
const labNode = document.createElement("div");
|
|
labNode.className = "world-map-lab";
|
|
labNode.style.left = `${lab.x}%`;
|
|
labNode.style.top = `${lab.y}%`;
|
|
labNode.dataset.poi = "laboratory";
|
|
const labNameEl = document.createElement("div");
|
|
labNameEl.className = "world-map-zoo-name";
|
|
labNameEl.textContent = lab.name ?? "Laboratoire";
|
|
labNode.appendChild(labNameEl);
|
|
const labSlotEl = document.createElement("div");
|
|
labSlotEl.className = "world-map-zoo-slot";
|
|
const labOffer = state.laboratoryOffer;
|
|
if (labOffer) {
|
|
labSlotEl.appendChild(createLabOfferButton(labOffer, ctx));
|
|
} else {
|
|
const iconEl = document.createElement("span");
|
|
iconEl.className = "world-map-zoo-icon";
|
|
iconEl.setAttribute("aria-hidden", "true");
|
|
iconEl.textContent = "🔬";
|
|
labSlotEl.appendChild(iconEl);
|
|
}
|
|
labNode.appendChild(labSlotEl);
|
|
worldMapEl.appendChild(labNode);
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapTruckEl: HTMLElement, state: import("./types.js").GameState, setState: () => void }} ctx
|
|
* @param {Array<import("./types.js").WorldZoo>} zoos
|
|
* @param {number} truckMs
|
|
* @returns {boolean} true if truck was updated (need setTimeout)
|
|
*/
|
|
function updateTruckSale(ctx, zoos, truckMs) {
|
|
const { worldMapTruckEl, state, setState } = ctx;
|
|
const truckSale = state.truckSale;
|
|
if (!truckSale || !truckSale.toZooId) return false;
|
|
const elapsed = Date.now() - (truckSale.startAt || 0);
|
|
if (elapsed >= truckMs) {
|
|
delete state.truckSale;
|
|
return false;
|
|
}
|
|
const fromZoo = zoos.find((z) => z.id === "player");
|
|
const toZoo = zoos.find((z) => z.id === truckSale.toZooId);
|
|
if (!fromZoo || !toZoo) return false;
|
|
const progress = elapsed / truckMs;
|
|
const x = fromZoo.x + (toZoo.x - fromZoo.x) * progress;
|
|
const y = fromZoo.y + (toZoo.y - fromZoo.y) * progress;
|
|
worldMapTruckEl.style.display = "block";
|
|
worldMapTruckEl.style.left = `${x}%`;
|
|
worldMapTruckEl.style.top = `${y}%`;
|
|
worldMapTruckEl.textContent = "🚚";
|
|
setTimeout(setState, 50);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapTruckEl: HTMLElement, state: import("./types.js").GameState, setState: () => void }} ctx
|
|
* @param {Array<import("./types.js").WorldZoo>} zoos
|
|
* @param {number} truckMs
|
|
* @returns {boolean} true if truck was updated (need setTimeout)
|
|
*/
|
|
function updateEggPurchaseTruck(ctx, zoos, truckMs) {
|
|
const { worldMapTruckEl, state, setState } = ctx;
|
|
const eggPurchase = state.eggPurchaseTruck;
|
|
const truckLevel = state.truckLevel ?? 1;
|
|
if (!eggPurchase || !eggPurchase.startAt) return false;
|
|
const durationMs = Math.max(1000, (truckMs * 2) / truckLevel);
|
|
const elapsed = Date.now() - eggPurchase.startAt;
|
|
if (elapsed >= durationMs) {
|
|
delete state.eggPurchaseTruck;
|
|
worldMapTruckEl.style.display = "none";
|
|
return false;
|
|
}
|
|
const fromZoo = zoos.find((z) => z.id === eggPurchase.fromZooId);
|
|
const toZoo = zoos.find((z) => z.id === eggPurchase.toZooId);
|
|
if (!fromZoo || !toZoo) return false;
|
|
const progress = elapsed / durationMs;
|
|
const leg = progress < 0.5 ? progress * 2 : (progress - 0.5) * 2;
|
|
const x = progress < 0.5
|
|
? fromZoo.x + (toZoo.x - fromZoo.x) * leg
|
|
: toZoo.x + (fromZoo.x - toZoo.x) * leg;
|
|
const y = progress < 0.5
|
|
? fromZoo.y + (toZoo.y - fromZoo.y) * leg
|
|
: toZoo.y + (fromZoo.y - toZoo.y) * leg;
|
|
worldMapTruckEl.style.display = "block";
|
|
worldMapTruckEl.style.left = `${x}%`;
|
|
worldMapTruckEl.style.top = `${y}%`;
|
|
worldMapTruckEl.textContent = "🚚";
|
|
setTimeout(setState, 50);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapTruckEl: HTMLElement, worldMapNpcTrucksEl: HTMLElement, state: import("./types.js").GameState, setState: () => void }} ctx
|
|
* @param {Array<import("./types.js").WorldZoo>} zoos
|
|
*/
|
|
function updatePlayerTruck(ctx, zoos) {
|
|
const { worldMapTruckEl } = ctx;
|
|
const truckMs = (GameConfig.WorldMap && GameConfig.WorldMap.TruckAnimationMs) || 2500;
|
|
if (updateTruckSale(ctx, zoos, truckMs)) return;
|
|
if (updateEggPurchaseTruck(ctx, zoos, truckMs)) return;
|
|
worldMapTruckEl.style.display = "none";
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapNpcTrucksEl: HTMLElement, state: import("./types.js").GameState, setState: () => void }} ctx
|
|
* @param {Array<import("./types.js").WorldZoo>} zoos
|
|
* @param {number} truckMs
|
|
*/
|
|
function renderNpcTrucks(ctx, zoos, truckMs) {
|
|
const { worldMapNpcTrucksEl, state } = ctx;
|
|
worldMapNpcTrucksEl.innerHTML = "";
|
|
const npcTrucks = state.worldTruckSales ?? [];
|
|
for (const truck of npcTrucks) {
|
|
const fromZoo = zoos.find((z) => z.id === truck.fromZooId);
|
|
const toZoo = zoos.find((z) => z.id === truck.toZooId);
|
|
if (fromZoo && toZoo) {
|
|
const elapsed = Date.now() - (truck.startAt || 0);
|
|
if (elapsed < truckMs) {
|
|
const progress = elapsed / truckMs;
|
|
const x = fromZoo.x + (toZoo.x - fromZoo.x) * progress;
|
|
const y = fromZoo.y + (toZoo.y - fromZoo.y) * progress;
|
|
const truckDiv = document.createElement("div");
|
|
truckDiv.className = "world-map-truck world-map-truck-npc";
|
|
truckDiv.style.left = `${x}%`;
|
|
truckDiv.style.top = `${y}%`;
|
|
truckDiv.textContent = "🚚";
|
|
worldMapNpcTrucksEl.appendChild(truckDiv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {{ worldMapEl: HTMLElement, worldMapTruckEl: HTMLElement, worldMapNpcTrucksEl: HTMLElement, state: import("./types.js").GameState, setState: () => void }} ctx
|
|
* @param {Array<import("./types.js").WorldZoo>} zoos
|
|
*/
|
|
export function renderTruckAndNpcTrucks(ctx, zoos) {
|
|
const truckMs = (GameConfig.WorldMap && GameConfig.WorldMap.TruckAnimationMs) || 2500;
|
|
const truckSale = ctx.state.truckSale;
|
|
const eggPurchase = ctx.state.eggPurchaseTruck;
|
|
updatePlayerTruck(ctx, zoos);
|
|
renderNpcTrucks(ctx, zoos, truckMs);
|
|
const npcTrucks = ctx.state.worldTruckSales ?? [];
|
|
if (npcTrucks.length > 0 || (truckSale && truckSale.toZooId) || (eggPurchase && eggPurchase.startAt)) {
|
|
setTimeout(ctx.setState, 50);
|
|
}
|
|
}
|