**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
275 lines
11 KiB
JavaScript
275 lines
11 KiB
JavaScript
import { refreshOffers, getSkillLevel } from "./conveyor.js";
|
|
import { getPlotUpgradeCost, getTruckUpgradeCost, getWorldMapUpgradeResearchCost } from "./economy.js";
|
|
import { getVisitorCount } from "./income.js";
|
|
import { getTimePhase } from "./time-weather.js";
|
|
import { canPrestige, doPrestige } from "./prestige.js";
|
|
import { playSound, isMusicEnabled } from "./audio.js";
|
|
import { questDescription, timePhaseLabel, weatherLabel, prestigeHint } from "./texts-fr.js";
|
|
import { GameConfig } from "./config.js";
|
|
import { renderWorldMap } from "./ui-world-map.js";
|
|
import { renderGrid } from "./ui-grid.js";
|
|
import { buildGameBar } from "./ui-render-gamebar.js";
|
|
import { buildWorldMapSection, buildGridSection } from "./ui-render-dom-panels.js";
|
|
|
|
/**
|
|
* @param {{ errEl: HTMLElement }} _setup
|
|
* @returns {{ tabsWrap: HTMLElement, tabContent: HTMLElement, panelZoo: HTMLElement, panelWorld: HTMLElement }}
|
|
*/
|
|
function createTabsStructure(_setup) {
|
|
const tabsWrap = document.createElement("div");
|
|
tabsWrap.className = "tabs-wrap";
|
|
tabsWrap.setAttribute("aria-label", "Carte du zoo et carte du monde");
|
|
const tabContent = document.createElement("div");
|
|
tabContent.className = "tabs-content";
|
|
const panelZoo = document.createElement("div");
|
|
panelZoo.className = "tab-panel active";
|
|
panelZoo.id = "tab-panel-zoo";
|
|
panelZoo.setAttribute("role", "tabpanel");
|
|
panelZoo.setAttribute("aria-labelledby", "view-toggle");
|
|
const panelWorld = document.createElement("div");
|
|
panelWorld.className = "tab-panel";
|
|
panelWorld.id = "tab-panel-world";
|
|
panelWorld.setAttribute("role", "tabpanel");
|
|
panelWorld.setAttribute("aria-labelledby", "view-toggle");
|
|
return { tabsWrap, tabContent, panelZoo, panelWorld };
|
|
}
|
|
|
|
/**
|
|
* @param {Array<{ descriptionKey: string, target: number, current: number, done: boolean }>} quests
|
|
* @returns {string}
|
|
*/
|
|
function formatQuestListHtml(quests) {
|
|
return (quests ?? []).map((q) => {
|
|
const desc = questDescription[q.descriptionKey];
|
|
const text = desc ? String(desc).replace("%d", String(q.target)) : q.descriptionKey;
|
|
const done = q.done ? " ✓" : "";
|
|
return `<div class="quest-item ${q.done ? "done" : ""}">${text} : ${q.current}/${q.target}${done}</div>`;
|
|
}).join("");
|
|
}
|
|
|
|
/**
|
|
* @param {object} gameBarResult
|
|
* @param {object} setup
|
|
* @returns {void}
|
|
*/
|
|
function updateStatusBody(gameBarResult, setup) {
|
|
const { state } = setup;
|
|
const { selected } = setup;
|
|
const {
|
|
statusBarCoins,
|
|
statusBarPlot,
|
|
statusBarCell,
|
|
statusBarSkill,
|
|
statusBarVisitors,
|
|
statusBarOffers,
|
|
statusBarTimeWeather,
|
|
musicBtn,
|
|
autoModeBtn,
|
|
prestigeBtn,
|
|
questListEl,
|
|
} = gameBarResult;
|
|
statusBarCoins.valueEl.textContent = String(Math.floor(state.coins));
|
|
statusBarPlot.valueEl.textContent = String(Math.floor(state.plotLevel));
|
|
statusBarCell.valueEl.textContent = `${Math.floor(selected.x)} ${Math.floor(selected.y)}`;
|
|
statusBarSkill.valueEl.textContent = String(Math.floor(getSkillLevel(state)));
|
|
const visitors = getVisitorCount(state);
|
|
statusBarVisitors.valueEl.textContent = String(Math.floor(visitors));
|
|
const offersCount = (state.conveyorOffers ?? []).length;
|
|
statusBarOffers.valueEl.textContent = String(Math.floor(offersCount));
|
|
const timePhase = getTimePhase(state.timeOfDay ?? 6);
|
|
const weatherVal = weatherLabel[state.weather] ?? state.weather;
|
|
statusBarTimeWeather.valueEl.textContent = `${timePhaseLabel[timePhase.phase]} · ${weatherVal}`;
|
|
statusBarTimeWeather.item.className = "status-bar-item status-bar-time-weather weather-" + (state.weather ?? "sun");
|
|
musicBtn.classList.toggle("muted", !isMusicEnabled());
|
|
autoModeBtn.setAttribute("aria-pressed", state.autoMode ? "true" : "false");
|
|
autoModeBtn.title = state.autoMode ? "Mode automatique (désactiver)" : "Mode automatique (activer)";
|
|
autoModeBtn.setAttribute("aria-label", state.autoMode ? "Mode automatique actif" : "Activer le mode automatique");
|
|
autoModeBtn.textContent = state.autoMode ? "🤖" : "✋";
|
|
prestigeBtn.title = String(prestigeHint).replace("%d", String(GameConfig.Prestige.MinCoinsToReset));
|
|
prestigeBtn.disabled = !canPrestige(state);
|
|
questListEl.innerHTML = formatQuestListHtml(state.quests);
|
|
}
|
|
|
|
/**
|
|
* @param {object} gameBarResult
|
|
* @param {object} setup
|
|
* @returns {() => void}
|
|
*/
|
|
function createUpdateStatus(gameBarResult, setup) {
|
|
return function updateStatus() {
|
|
updateStatusBody(gameBarResult, setup);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {object} opts
|
|
* @returns {() => void}
|
|
*/
|
|
function createFullRender(opts) {
|
|
const {
|
|
setup,
|
|
sellZone,
|
|
plotUpgradeZone,
|
|
worldMapUpgradeZone,
|
|
worldMapCounters,
|
|
worldMapCtx,
|
|
gridCtx,
|
|
updateStatus,
|
|
} = opts;
|
|
const { state } = setup;
|
|
return function fullRender() {
|
|
setup.clampSelection();
|
|
updateStatus();
|
|
const canUpTruck = (state.truckLevel ?? 1) < ((GameConfig.Truck && GameConfig.Truck.MaxLevel) || 5)
|
|
&& state.coins >= getTruckUpgradeCost(state.truckLevel ?? 1);
|
|
sellZone.classList.toggle("can-upgrade", canUpTruck);
|
|
const truckArrow = sellZone.querySelector(".sell-zone-upgrade-arrow");
|
|
if (truckArrow) truckArrow.style.display = canUpTruck ? "" : "none";
|
|
const plotMaxLevel = GameConfig.Plot.MaxLevel || 8;
|
|
const canUpgradePlot = (state.plotLevel ?? 1) < plotMaxLevel && state.coins >= getPlotUpgradeCost(state.plotLevel ?? 1);
|
|
plotUpgradeZone.classList.toggle("can-upgrade", canUpgradePlot);
|
|
const plotArrow = plotUpgradeZone.querySelector(".plot-upgrade-zone-arrow");
|
|
if (plotArrow) plotArrow.style.display = canUpgradePlot ? "" : "none";
|
|
updateWorldMapUpgradeAndCounters(worldMapUpgradeZone, worldMapCounters, state);
|
|
const eggPurchase = state.eggPurchaseTruck;
|
|
if (eggPurchase && eggPurchase.startAt) {
|
|
const truckLevel = state.truckLevel ?? 1;
|
|
const baseMs = (GameConfig.WorldMap && GameConfig.WorldMap.TruckAnimationMs) || 2500;
|
|
const durationMs = Math.max(1000, (baseMs * 2) / truckLevel);
|
|
if (Date.now() - eggPurchase.startAt >= durationMs) delete state.eggPurchaseTruck;
|
|
}
|
|
renderWorldMap(worldMapCtx);
|
|
renderGrid(gridCtx);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} worldMapUpgradeZone
|
|
* @param {HTMLElement} worldMapCounters
|
|
* @param {import("./types.js").GameState} state
|
|
* @returns {void}
|
|
*/
|
|
function updateWorldMapUpgradeAndCounters(worldMapUpgradeZone, worldMapCounters, state) {
|
|
const mapCfg = GameConfig.WorldMap && GameConfig.WorldMap.MapUpgrade;
|
|
const mapMaxLevel = mapCfg ? mapCfg.MaxLevel : 5;
|
|
const currentMapLevel = state.worldMapLevel ?? 1;
|
|
const mapResearchCost = getWorldMapUpgradeResearchCost(currentMapLevel);
|
|
const canUpgradeMap = currentMapLevel < mapMaxLevel && (state.researchPoints ?? 0) >= mapResearchCost;
|
|
worldMapUpgradeZone.classList.toggle("can-upgrade", canUpgradeMap);
|
|
worldMapUpgradeZone.title = currentMapLevel < mapMaxLevel
|
|
? `Agrandir la carte (${mapResearchCost} unités de recherche)`
|
|
: "Agrandir la carte";
|
|
const mapCostEl = worldMapUpgradeZone.querySelector(".world-map-upgrade-zone-cost");
|
|
if (mapCostEl) mapCostEl.textContent = currentMapLevel < mapMaxLevel ? ` ${mapResearchCost} 🔬` : "";
|
|
const mapArrow = worldMapUpgradeZone.querySelector(".world-map-upgrade-zone-arrow");
|
|
if (mapArrow) mapArrow.style.display = canUpgradeMap ? "" : "none";
|
|
const babiesForSale = (state.saleListings ?? []).filter((s) => s.isBaby).length;
|
|
const animalsForSale = (state.saleListings ?? []).filter((s) => !s.isBaby).length;
|
|
const labsCount = GameConfig.WorldMap && GameConfig.WorldMap.Laboratory ? 1 : 0;
|
|
const zoosCount = (state.worldZoos ?? []).length;
|
|
const citiesCount = (GameConfig.WorldMap && GameConfig.WorldMap.Cities) ? GameConfig.WorldMap.Cities.length : 0;
|
|
worldMapCounters.textContent = "";
|
|
const counterEntries = [
|
|
["Bébés à vendre", babiesForSale],
|
|
["Animaux à vendre", animalsForSale],
|
|
["Laboratoires", labsCount],
|
|
["Zoos", zoosCount],
|
|
["Villes", citiesCount],
|
|
];
|
|
for (const [label, value] of counterEntries) {
|
|
const span = document.createElement("span");
|
|
span.className = "world-map-counter";
|
|
span.title = label;
|
|
span.setAttribute("aria-label", `${label}: ${value}`);
|
|
span.textContent = `${label}: ${value}`;
|
|
worldMapCounters.appendChild(span);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {{ setup: object, gameBarResult: object, worldMapResult: object, gridResult: object }} opts
|
|
* @returns {{ worldMapCtx: object, gridCtx: object }}
|
|
*/
|
|
function buildFinishContexts(opts) {
|
|
const { setup, worldMapResult, gridResult } = opts;
|
|
const worldMapCtx = {
|
|
worldMapEl: worldMapResult.worldMapEl,
|
|
worldMapTruckEl: worldMapResult.worldMapTruckEl,
|
|
worldMapNpcTrucksEl: worldMapResult.worldMapNpcTrucksEl,
|
|
state: setup.state,
|
|
setState: setup.setState,
|
|
setError: setup.setError,
|
|
playSound,
|
|
animalEmoji: setup.animalEmoji,
|
|
pendingTokenByEggType: setup.pendingTokenByEggType,
|
|
};
|
|
const gridCtx = {
|
|
state: setup.state,
|
|
setState: setup.setState,
|
|
setError: setup.setError,
|
|
playSound,
|
|
gridEl: gridResult.gridEl,
|
|
getHatched: setup.getHatched,
|
|
selected: setup.selected,
|
|
emptyCellChoice: setup.emptyCellChoiceRef,
|
|
selectedTokenId: setup.selectedTokenIdRef,
|
|
lastActionWasDrop: setup.lastActionWasDropRef,
|
|
clampSelection: setup.clampSelection,
|
|
animalEmoji: setup.animalEmoji,
|
|
};
|
|
return { worldMapCtx, gridCtx };
|
|
}
|
|
|
|
/**
|
|
* @param {{ root: HTMLElement, setup: object, gameBarResult: object, worldMapResult: object, gridResult: object, updateStatus: () => void }} opts
|
|
* @returns {() => void}
|
|
*/
|
|
function finishBuildUIDOM(opts) {
|
|
const { setup, gameBarResult, worldMapResult, gridResult, updateStatus } = opts;
|
|
const { worldMapCtx, gridCtx } = buildFinishContexts(opts);
|
|
renderWorldMap(worldMapCtx);
|
|
renderGrid(gridCtx);
|
|
gameBarResult.prestigeBtn.addEventListener("click", () => {
|
|
if (!canPrestige(setup.state)) return;
|
|
doPrestige(setup.state);
|
|
refreshOffers(setup.state, Math.floor(Date.now() / 1000));
|
|
setup.setError("");
|
|
playSound("upgrade");
|
|
setup.setState();
|
|
});
|
|
const fullRender = createFullRender({
|
|
setup,
|
|
sellZone: gridResult.sellZone,
|
|
plotUpgradeZone: gridResult.plotUpgradeZone,
|
|
worldMapUpgradeZone: worldMapResult.worldMapUpgradeZone,
|
|
worldMapCounters: worldMapResult.worldMapCounters,
|
|
worldMapCtx,
|
|
gridCtx,
|
|
updateStatus,
|
|
});
|
|
fullRender();
|
|
return fullRender;
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} root
|
|
* @param {object} setup
|
|
* @returns {() => void}
|
|
*/
|
|
export function buildUIDOM(root, setup) {
|
|
const tabs = createTabsStructure(setup);
|
|
const { panelZoo, panelWorld, tabsWrap, tabContent } = tabs;
|
|
const gameBarResult = buildGameBar(setup, panelZoo, panelWorld);
|
|
const worldMapResult = buildWorldMapSection(panelWorld, setup);
|
|
const gridResult = buildGridSection(panelZoo, setup);
|
|
tabsWrap.appendChild(setup.errEl);
|
|
tabContent.appendChild(panelZoo);
|
|
tabContent.appendChild(panelWorld);
|
|
tabsWrap.appendChild(tabContent);
|
|
gameBarResult.gameBarActions.insertBefore(gameBarResult.viewSwitcherWrap, gameBarResult.gameBarActions.firstChild);
|
|
root.appendChild(gameBarResult.gameBar);
|
|
root.appendChild(tabsWrap);
|
|
const updateStatus = createUpdateStatus(gameBarResult, setup);
|
|
return finishBuildUIDOM({ root, setup, gameBarResult, worldMapResult, gridResult, updateStatus });
|
|
}
|