Lint: fix errors and remove unused variables

**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
This commit is contained in:
ncantu
2026-03-04 15:32:27 +01:00
parent d8a55daf3f
commit c7d389ecbb
57 changed files with 4664 additions and 3049 deletions

View File

@@ -0,0 +1,213 @@
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);
}
}