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

@@ -1,35 +1,25 @@
import { LootTables } from "./loot-tables.js";
import { getIncomeMultiplier } from "./mutation-rules.js";
import { getLevelMultiplier, getSellValue } from "./economy.js";
import { getLevelMultiplier } from "./economy.js";
import { GameConfig } from "./config.js";
import { getPrestigeIncomeMultiplier } from "./prestige.js";
import { isOriginCell } from "./grid-utils.js";
import { getOriginAnimalCount } from "./food.js";
import { getCurrentSeason, getSeasonVisitorMultiplier, getSeasonTicketPriceMultiplier } from "./seasons.js";
import { getTotalAnimalValue } from "./income-value.js";
import { getAttractivityBase } from "./income-attractivity.js";
/**
* Total sell value of all animals in the zoo (used for visitor attraction). Counts each animal block once (origin cell only).
* @param {import("./types.js").GameState} state
* Visitor demand multiplier by time of day (spec visiteur: 08h-10h faible, 10h-16h fort, 16h-18h décroissant, >18h nul).
* @param {number} timeOfDay 0..24
* @returns {number}
*/
function getTotalAnimalValue(state) {
let total = 0;
for (const [key, cell] of Object.entries(state.grid.cells)) {
if (cell.kind !== "animal" || !isOriginCell(key, cell)) {
// skip non-origin animals
} else {
const animalDef = LootTables.Animals[cell.id];
if (animalDef !== null && animalDef !== undefined) {
const mutationMult = getIncomeMultiplier(cell.mutation);
total += getSellValue(
animalDef.baseIncomePerSecond,
cell.level,
mutationMult,
animalDef.sellFactor
);
}
}
}
return total;
function getVisitorDemandHourMultiplier(timeOfDay) {
const t = timeOfDay % 24;
if (t < 8 || t >= 20) return 0;
if (t >= 8 && t < 10) return 0.5;
if (t >= 10 && t < 16) return 1;
if (t >= 16 && t < 18) return 0.7;
return 0.3;
}
/**
@@ -95,22 +85,41 @@ function getStagnationMultiplier(state, nowUnix) {
}
/**
* Stay duration multiplier from boutiques and animal diversity (visitors stay longer).
* Shop bonus component of stay multiplier (souvenir shops).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getStayMultiplier(state) {
function getStayMultiplierShopBonus(state) {
let shopBonus = 0;
for (const cell of Object.values(state.grid.cells)) {
if (cell !== null && cell !== undefined && cell.kind === "souvenirShop") {
shopBonus += (cell.level ?? 1) * (GameConfig.Visitor.StayMultiplierPerShopLevel ?? 0.15);
}
}
return shopBonus;
}
/**
* Diversity bonus component (species count).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getStayMultiplierDiversityBonus(state) {
const speciesSet = new Set();
for (const [key, cell] of Object.entries(state.grid.cells)) {
if (cell !== null && cell !== undefined && cell.kind === "animal" && isOriginCell(key, cell)) speciesSet.add(cell.id);
}
const diversityBonus = speciesSet.size * (GameConfig.Visitor.StayMultiplierPerSpecies ?? 0.02);
return speciesSet.size * (GameConfig.Visitor.StayMultiplierPerSpecies ?? 0.02);
}
/**
* Stay duration multiplier from boutiques and animal diversity (visitors stay longer).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getStayMultiplier(state) {
const shopBonus = getStayMultiplierShopBonus(state);
const diversityBonus = getStayMultiplierDiversityBonus(state);
return Math.max(0.5, 1 + shopBonus + diversityBonus);
}
@@ -144,69 +153,155 @@ function getVisitorDemand(state, nowUnix) {
demand *= 1 + cityAttraction;
demand *= 1 + animalValue * animalValueScale;
demand *= getStagnationMultiplier(state, nowUnix);
const seasonMult = getSeasonVisitorMultiplier(getCurrentSeason(state));
demand *= seasonMult;
const hourMult = getVisitorDemandHourMultiplier(state.timeOfDay ?? 6);
demand *= hourMult;
return Math.max(0, Math.floor(demand));
}
/**
* Remove visitors who exceeded stay duration.
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
*/
function filterExpiredVisitors(state, nowUnix) {
const stayDuration = getStayDurationSeconds(state);
state.visitorArrivals = (state.visitorArrivals ?? []).filter(
(v) => nowUnix < v.arrivedAt + stayDuration
);
}
/**
* Whether we are within opening hours for new entries.
* @param {import("./types.js").GameState} state
* @returns {boolean}
*/
function isVisitorOpeningHours(state) {
const timeOfDay = state.timeOfDay ?? 6;
const openHour = GameConfig.Billeterie?.OpenHour ?? 8;
const closeHour = GameConfig.Billeterie?.CloseHour ?? 20;
return timeOfDay >= openHour && timeOfDay < closeHour;
}
/**
* Update visitor entities: remove those who exceeded stay duration, add new arrivals up to min(cap, demand).
* New arrivals only during opening hours (OpenHourCloseHour). Max MaxEntryPerSecond new visitors per second.
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
*/
export function tickVisitorArrivals(state, nowUnix) {
state.visitorArrivals = state.visitorArrivals ?? [];
const stayDuration = getStayDurationSeconds(state);
state.visitorArrivals = state.visitorArrivals.filter(
(v) => nowUnix < v.arrivedAt + stayDuration
);
filterExpiredVisitors(state, nowUnix);
if (!isVisitorOpeningHours(state)) return;
const demand = getVisitorDemand(state, nowUnix);
const cap = getBilleterieCapacity(state);
const target = Math.min(cap, demand);
const current = state.visitorArrivals.length;
for (let i = 0; i < target - current; i++) {
const maxToAdd = target - current;
if (maxToAdd <= 0) return;
const maxPerSecond = GameConfig.Billeterie?.MaxEntryPerSecond ?? 1;
const secondsPerTick = GameConfig.IncomeTickMs / 1000;
const maxThisTick = Math.min(maxToAdd, Math.ceil(maxPerSecond * secondsPerTick));
for (let i = 0; i < maxThisTick; i++) {
state.visitorArrivals.push({ arrivedAt: nowUnix });
}
}
/**
* Raw visitor count from animals and plot when no billeterie (for fallback).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getVisitorCountFallback(state) {
let animalCount = 0;
for (const [key, cell] of Object.entries(state.grid.cells)) {
if (cell.kind === "animal" && isOriginCell(key, cell)) animalCount += 1;
}
const visitorsPerAnimal = GameConfig.Visitor.VisitorsPerAnimal;
const plotBonus = (state.plotLevel ?? 1) * GameConfig.Visitor.PlotLevelBonus;
return Math.max(0, Math.floor(animalCount * visitorsPerAnimal + plotBonus));
}
/**
* Visitor count capped by billeterie.
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getVisitorCountCapped(state) {
const arrivals = state.visitorArrivals ?? [];
let visitorCount = arrivals.length;
if (visitorCount === 0 && getBilleterieCapacity(state) === 0) {
visitorCount = getVisitorCountFallback(state);
}
const billeterieCap = getBilleterieCapacity(state);
if (billeterieCap > 0 && visitorCount > billeterieCap) visitorCount = billeterieCap;
return visitorCount;
}
/**
* Luxury shop multiplier component for souvenir bonus (>= 1).
* @returns {number}
*/
function getLuxuryShopMultiplier() {
const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
const luxuryShopMult = GameConfig.Visitor.LuxuryShopMultiplier ?? 1;
if (luxuryChance > 0 && luxuryShopMult > 1) {
return 1 + luxuryChance * (luxuryShopMult - 1);
}
return 1;
}
/**
* Souvenir shop bonus multiplier (>= 1).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getSouvenirBonus(state) {
let shopCount = 0;
for (const cell of Object.values(state.grid.cells)) {
if (cell && cell.kind === "souvenirShop") shopCount += (cell.level ?? 1);
}
if (shopCount === 0) return 1;
const bonusPerShop = GameConfig.Visitor.SouvenirShopBonusPerShop ?? 0.2;
return (1 + shopCount * bonusPerShop) * getLuxuryShopMultiplier();
}
/**
* Luxury entry multiplier (>= 1).
* @returns {number}
*/
function getLuxuryEntryMultiplier() {
const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
const luxuryEntryMult = GameConfig.Visitor.LuxuryEntryMultiplier ?? 1;
if (luxuryChance > 0 && luxuryEntryMult > 1) {
return 1 + luxuryChance * (luxuryEntryMult - 1);
}
return 1;
}
/**
* Payment per visitor (base × souvenir × luxury × season).
* @param {import("./types.js").GameState} state
* @returns {number}
*/
function getPaymentPerVisitor(state) {
let paymentPerVisitor = GameConfig.Visitor.BasePaymentPerVisitor;
paymentPerVisitor *= getSouvenirBonus(state);
paymentPerVisitor *= getLuxuryEntryMultiplier();
const ticketSeasonMult = getSeasonTicketPriceMultiplier(getCurrentSeason(state));
paymentPerVisitor *= ticketSeasonMult;
return paymentPerVisitor;
}
/**
* Visitor count and average payment per visitor per second. Includes luxury guest effect (LuxuryGuestChance, LuxuryEntryMultiplier, LuxuryShopMultiplier) in the average.
* @param {import("./types.js").GameState} state
* @returns {{ visitorCount: number, paymentPerVisitor: number }}
*/
function getVisitorParams(state) {
const arrivals = state.visitorArrivals ?? [];
let visitorCount = arrivals.length;
if (visitorCount === 0 && getBilleterieCapacity(state) === 0) {
let animalCount = 0;
for (const [key, cell] of Object.entries(state.grid.cells)) {
if (cell.kind === "animal" && isOriginCell(key, cell)) animalCount += 1;
}
const visitorsPerAnimal = GameConfig.Visitor.VisitorsPerAnimal;
const plotBonus = (state.plotLevel ?? 1) * GameConfig.Visitor.PlotLevelBonus;
visitorCount = Math.max(0, Math.floor(animalCount * visitorsPerAnimal + plotBonus));
}
const billeterieCap = getBilleterieCapacity(state);
if (billeterieCap > 0 && visitorCount > billeterieCap) visitorCount = billeterieCap;
let paymentPerVisitor = GameConfig.Visitor.BasePaymentPerVisitor;
let souvenirBonus = 1;
let shopCount = 0;
for (const cell of Object.values(state.grid.cells)) {
if (cell && cell.kind === "souvenirShop") shopCount += (cell.level ?? 1);
}
if (shopCount > 0) {
const bonusPerShop = GameConfig.Visitor.SouvenirShopBonusPerShop ?? 0.2;
souvenirBonus = 1 + shopCount * bonusPerShop;
const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
const luxuryShopMult = GameConfig.Visitor.LuxuryShopMultiplier ?? 1;
if (luxuryChance > 0 && luxuryShopMult > 1) {
souvenirBonus *= 1 + luxuryChance * (luxuryShopMult - 1);
}
}
paymentPerVisitor *= souvenirBonus;
const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
const luxuryEntryMult = GameConfig.Visitor.LuxuryEntryMultiplier ?? 1;
if (luxuryChance > 0 && luxuryEntryMult > 1) {
paymentPerVisitor *= 1 + luxuryChance * (luxuryEntryMult - 1);
}
const visitorCount = getVisitorCountCapped(state);
const paymentPerVisitor = getPaymentPerVisitor(state);
return { visitorCount, paymentPerVisitor };
}
@@ -220,28 +315,7 @@ export function getVisitorCount(state) {
* @returns {number}
*/
export function getAttractivityScore(state) {
const value = getTotalAnimalValue(state);
const originCount = getOriginAnimalCount(state);
const grid = state.grid;
const cellCount = grid.width * grid.height;
const fillRate = cellCount > 0 ? originCount / cellCount : 0;
const speciesSet = new Set();
let raritySum = 0;
for (const [key, cell] of Object.entries(state.grid.cells)) {
if (cell === null || cell === undefined || cell.kind !== "animal" || !isOriginCell(key, cell)) {
// skip
} else {
speciesSet.add(cell.id);
const def = LootTables.Animals[cell.id];
if (def) raritySum += def.rarityLevel ?? 1;
}
}
const speciesCount = speciesSet.size;
const avgRarity = originCount > 0 ? raritySum / originCount : 0;
const valueNorm = value * 0.001;
const speciesNorm = speciesCount * 2;
const rarityNorm = avgRarity * 0.5;
const fillNorm = fillRate * 10;
const { valueNorm, speciesNorm, rarityNorm, fillNorm } = getAttractivityBase(state);
let score = valueNorm + speciesNorm + rarityNorm + fillNorm;
const deathPenalty = GameConfig.Visitor?.AttractivityDeathPenalty ?? 0.5;
const birthBonus = GameConfig.Visitor?.AttractivityBirthBonus ?? 0.2;