Files
builazoo/web/js/conveyor.js
ncantu c7d389ecbb 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
2026-03-04 15:32:27 +01:00

223 lines
8.3 KiB
JavaScript

import { GameConfig } from "./config.js";
import { LootTables, getAnimalToEggTypeMap } from "./loot-tables.js";
import { defaultAnimalWeights } from "./state.js";
import { getZooSkillLevel } from "./bot-zoo.js";
import { getConveyorUpgradeCost, getSchoolUpgradeCost, getTruckUpgradeCost } from "./economy.js";
import { pickId } from "./weighted-random.js";
import { cellKey } from "./grid-utils.js";
const ANIMAL_TO_EGG_TYPE = getAnimalToEggTypeMap();
const DEFAULT_ZOO_WEIGHTS = defaultAnimalWeights();
/**
* Skill level = max level among school cells, or state.conveyorLevel for backward compat.
* @param {import("./types.js").GameState} state
* @returns {number}
*/
export function getSkillLevel(state) {
let maxSchool = 0;
for (const cell of Object.values(state.grid.cells)) {
if (cell.kind === "school") maxSchool = Math.max(maxSchool, cell.level);
}
return maxSchool || state.conveyorLevel || 1;
}
/**
* @param {number} conveyorLevel
* @returns {Array<{ id: string, weight: number }>}
*/
function getEligibleEggTypes(conveyorLevel) {
const entries = [];
for (const [eggType, def] of Object.entries(LootTables.EggTypes)) {
if (conveyorLevel >= def.minConveyorLevel)
entries.push({ id: eggType, weight: 100 - def.minConveyorLevel * 8 });
}
return entries;
}
/**
* Player zoo weights from grid: more animals of a type => more of that egg type at own zoo.
* @param {import("./types.js").GameState} state
* @returns {Record<string, number>}
*/
export function getPlayerZooWeights(state) {
const colorKeys = Object.keys(LootTables.EggTypes);
const w = Object.fromEntries(colorKeys.map((k) => [k, 0]));
for (const cell of Object.values(state.grid.cells)) {
if (cell.kind === "animal") {
const eggType = ANIMAL_TO_EGG_TYPE[cell.id];
if (eggType) w[eggType] = (w[eggType] ?? 0) + 1;
}
}
return w;
}
/**
* @param {import("./types.js").GameState} state
* @param {object} zoo
* @param {string} eggType
* @returns {{ skillLevel: number, weight: number } | null}
*/
function getZooSkillAndWeightForEgg(state, zoo, eggType) {
const skillLevel = zoo.id === "player" ? getSkillLevel(state) : getZooSkillLevel(zoo);
const eggDef = LootTables.EggTypes[eggType];
const minLevel = eggDef ? eggDef.minConveyorLevel : 1;
if (skillLevel < minLevel) return null;
const playerWeights = zoo.id === "player" ? getPlayerZooWeights(state) : (zoo.animalWeights ?? {});
const weight = playerWeights[eggType] ?? 0;
return weight > 0 ? { skillLevel, weight } : null;
}
/**
* Zoos that can offer this egg type (skill level allows it and zoo has weight for it).
* @param {import("./types.js").GameState} state
* @param {string} eggType
* @returns {Array<{ id: string, weight: number }>}
*/
function getZoosForEggType(state, eggType) {
const zoos = state.worldZoos ?? [{ id: "player", name: "Mon zoo", x: 25, y: 50, animalWeights: DEFAULT_ZOO_WEIGHTS }];
const entries = [];
for (const zoo of zoos) {
const info = getZooSkillAndWeightForEgg(state, zoo, eggType);
if (info) entries.push({ id: zoo.id, weight: info.weight });
}
if (entries.length === 0) entries.push({ id: "player", weight: 1 });
return entries;
}
/**
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
*/
export function refreshOffers(state, nowUnix) {
const rng = () => Math.random();
const skillLevel = getSkillLevel(state);
const pool = getEligibleEggTypes(skillLevel);
const offers = [];
for (let i = 0; i < GameConfig.Conveyor.OfferCount; i++) {
const eggType = pickId(rng, pool);
const eggDef = LootTables.EggTypes[eggType];
const zooPool = getZoosForEggType(state, eggType);
const zooId = zooPool.length ? pickId(rng, zooPool) : "player";
offers.push({ eggType, price: eggDef.price, zooId });
}
const animalIds = Object.keys(LootTables.Animals ?? {});
if (animalIds.length > 0) {
const babyAnimalId = animalIds[Math.floor(rng() * animalIds.length)];
const babyDef = LootTables.Animals[babyAnimalId];
const babyPrice = babyDef ? Math.floor(50 + (babyDef.rarityLevel ?? 1) * 30) : 80;
offers.push({ type: "baby", animalId: babyAnimalId, price: babyPrice, zooId: "player" });
const adultAnimalId = animalIds[Math.floor(rng() * animalIds.length)];
const adultDef = LootTables.Animals[adultAnimalId];
const adultPrice = adultDef ? Math.floor(80 + (adultDef.rarityLevel ?? 1) * 40) : 120;
offers.push({ type: "animal", animalId: adultAnimalId, price: adultPrice, zooId: "player" });
}
state.conveyorOffers = offers;
state.lastOfferRefreshAt = nowUnix;
}
/**
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
* @returns {boolean}
*/
export function shouldRefresh(state, nowUnix) {
return nowUnix - state.lastOfferRefreshAt >= GameConfig.Conveyor.RefreshSeconds;
}
/**
* Upgrade school at cell (x,y). Returns [ok, reason].
* @param {import("./types.js").GameState} state
* @param {number} x
* @param {number} y
* @returns {[boolean, string?]}
*/
export function tryUpgradeSchool(state, x, y) {
const key = cellKey(x, y);
const cell = state.grid.cells[key];
if (cell === null || cell === undefined || cell.kind !== "school") return [false, "NoSchool"];
const maxLevel = (GameConfig.School && GameConfig.School.MaxLevel) || GameConfig.Conveyor.MaxLevel;
if (cell.level >= maxLevel) return [false, "ConveyorMaxLevel"];
const cost = getSchoolUpgradeCost(cell.level);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
cell.level += 1;
state.conveyorLevel = getSkillLevel(state);
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.conveyorUpgrades = (state.stats.conveyorUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* Upgrade truck. Returns [ok, reason].
* @param {import("./types.js").GameState} state
* @returns {[boolean, string?]}
*/
export function tryUpgradeTruck(state) {
const level = state.truckLevel ?? 1;
const maxLevel = (GameConfig.Truck && GameConfig.Truck.MaxLevel) || 5;
if (level >= maxLevel) return [false, "TruckMaxLevel"];
const cost = getTruckUpgradeCost(level);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
state.truckLevel = level + 1;
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.truckUpgrades = (state.stats.truckUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* @param {import("./types.js").GameState} state
* @returns {[boolean, string?]}
*/
export function tryUpgrade(state) {
if (state.conveyorLevel >= GameConfig.Conveyor.MaxLevel) return [false, "ConveyorMaxLevel"];
const cost = getConveyorUpgradeCost(state.conveyorLevel);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
state.conveyorLevel += 1;
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.conveyorUpgrades = (state.stats.conveyorUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* @param {import("./types.js").GameState} state
* @param {string} eggType
* @returns {{ eggType: string, price: number, zooId?: string } | null}
*/
export function findOffer(state, eggType) {
return state.conveyorOffers.find((o) => o.eggType === eggType) ?? null;
}
/**
* @param {import("./types.js").GameState} state
* @param {string} [animalId]
* @returns {{ type: "baby", animalId: string, price: number, zooId?: string } | null}
*/
export function findBabyOffer(state, animalId) {
const o = state.conveyorOffers.find((x) => x.type === "baby" && (animalId === null || animalId === undefined || x.animalId === animalId));
return o && o.type === "baby" ? o : null;
}
/**
* @param {import("./types.js").GameState} state
* @param {string} [animalId]
* @returns {{ type: "animal", animalId: string, price: number, zooId?: string } | null}
*/
export function findAnimalOffer(state, animalId) {
const o = state.conveyorOffers.find((x) => x.type === "animal" && (animalId === null || animalId === undefined || x.animalId === animalId));
return o && o.type === "animal" ? o : null;
}
/**
* Pick another zoo (not player) for truck sale animation.
* @param {import("./types.js").GameState} state
* @returns {string}
*/
export function pickSaleTargetZoo(state) {
const zoos = (state.worldZoos ?? []).filter((z) => z.id !== "player");
if (zoos.length === 0) return "player";
return zoos[Math.floor(Math.random() * zoos.length)].id;
}