**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
254 lines
8.3 KiB
JavaScript
254 lines
8.3 KiB
JavaScript
import { GameConfig } from "./config.js";
|
|
import { plotSizeFromLevel } from "./grid-utils.js";
|
|
import { LootTables, getColorNames, zeroAnimalWeights } from "./loot-tables.js";
|
|
import { ensureBotState } from "./bot-zoo.js";
|
|
import { buildDefaultRow1Cells, addStarterAnimals } from "./default-grid-layout.js";
|
|
|
|
export function defaultAnimalWeights() {
|
|
const w = zeroAnimalWeights();
|
|
w[getColorNames()[0]] = 1;
|
|
return w;
|
|
}
|
|
|
|
export function normalizeZooWeights(legacy) {
|
|
if (!legacy || typeof legacy !== "object") return defaultAnimalWeights();
|
|
const keys = getColorNames();
|
|
const w = zeroAnimalWeights();
|
|
const map = { Basic: keys[0], Ocean: keys[5], Mountain: keys[10] };
|
|
for (const [oldKey, val] of Object.entries(legacy)) {
|
|
const newKey = map[oldKey] ?? oldKey;
|
|
if (keys.includes(newKey)) w[newKey] = Number(val) || 0;
|
|
}
|
|
return w;
|
|
}
|
|
|
|
/**
|
|
* @returns {import("./types.js").GameState}
|
|
*/
|
|
export function defaultState() {
|
|
const [width, height] = plotSizeFromLevel(1);
|
|
const worldZoos = buildDefaultWorldZoos();
|
|
const cells = buildDefaultCells();
|
|
const state = buildStatePayload(width, height, worldZoos, cells);
|
|
addStarterAnimals(state);
|
|
return state;
|
|
}
|
|
|
|
function buildDefaultWorldZoos() {
|
|
const configZoos = GameConfig.WorldMap?.Zoos;
|
|
if (configZoos && configZoos.length > 0) {
|
|
const worldZoos = configZoos.map((z, i) => ({
|
|
id: z.id,
|
|
name: z.name,
|
|
x: z.x,
|
|
y: z.y,
|
|
animalWeights: i === 0 ? defaultAnimalWeights() : normalizeZooWeights(z.animalWeights),
|
|
}));
|
|
worldZoos.forEach((zoo) => ensureBotState(zoo, zoo.id === "player"));
|
|
return worldZoos;
|
|
}
|
|
return [{ id: "player", name: "Mon zoo", x: 25, y: 50, animalWeights: defaultAnimalWeights() }];
|
|
}
|
|
|
|
function buildDefaultCells() {
|
|
return buildDefaultRow1Cells();
|
|
}
|
|
|
|
function getDefaultStats() {
|
|
return { eggsPlaced: 0, animalsSold: 0, conveyorUpgrades: 0, plotUpgrades: 0, truckUpgrades: 0, coinsEarned: 0 };
|
|
}
|
|
|
|
function buildStatePayload(width, height, worldZoos, cells) {
|
|
const grid = { width, height, cells };
|
|
return {
|
|
version: GameConfig.StateVersion,
|
|
coins: 200,
|
|
conveyorLevel: 1,
|
|
plotLevel: 1,
|
|
truckLevel: 1,
|
|
grid,
|
|
pendingEggTokens: [],
|
|
nextTokenId: 1,
|
|
conveyorOffers: [],
|
|
lastOfferRefreshAt: 0,
|
|
worldZoos,
|
|
truckSale: undefined,
|
|
worldTruckSales: [],
|
|
lastEvolutionAt: Math.floor(Date.now() / 1000),
|
|
laboratoryOffer: null,
|
|
prestigeLevel: 0,
|
|
timeOfDay: 6,
|
|
gameDayTotal: 0,
|
|
lastSeason: "spring",
|
|
weather: "sun",
|
|
lastWeatherChangeAt: 0,
|
|
quests: [],
|
|
lastQuestDay: "",
|
|
stats: getDefaultStats(),
|
|
mapZoom: 1, mapPanX: 0, mapPanY: 0, worldMapLevel: 1,
|
|
autoMode: false,
|
|
autoModeProfile: "balanced",
|
|
researchPoints: 0,
|
|
pendingBabies: [],
|
|
receptionAnimals: [],
|
|
saleListings: [],
|
|
deathCountRecent: 0,
|
|
birthCount: 0,
|
|
reproductionTimers: [],
|
|
visitorArrivals: [],
|
|
};
|
|
}
|
|
|
|
const STORAGE_KEY = "builazoo_state";
|
|
|
|
/**
|
|
* @param {import("./types.js").GameState} state
|
|
*/
|
|
export function saveState(state) {
|
|
try {
|
|
const toSave = { ...state };
|
|
delete toSave.autoProfilePickerOpen;
|
|
delete toSave.autoProfilePickerFamily;
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
|
|
} catch (e) {
|
|
console.error("saveState failed", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {import("./types.js").GameState | null}
|
|
*/
|
|
export function loadState() {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw === null || raw === undefined) return null;
|
|
const data = JSON.parse(raw);
|
|
if (!data || typeof data.coins !== "number" || !data.grid || typeof data.grid.cells !== "object") return null;
|
|
applyLoadStateDefaults(data);
|
|
normalizeLoadedCells(data.grid.cells);
|
|
ensureSchoolCell(data);
|
|
return data;
|
|
} catch (e) {
|
|
console.error("loadState failed", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function applyLoadStateDefaults(data) {
|
|
applyLoadStateWorldZoos(data);
|
|
applyLoadStateScalarDefaults(data);
|
|
applyLoadStateLegacyCells(data);
|
|
}
|
|
|
|
function applyLoadStateWorldZoos(data) {
|
|
if (data.coins < 100) data.coins = 200;
|
|
if (data.pendingEggTokens === null || data.pendingEggTokens === undefined) data.pendingEggTokens = [];
|
|
if (data.conveyorOffers === null || data.conveyorOffers === undefined) data.conveyorOffers = [];
|
|
data.conveyorOffers = data.conveyorOffers.map((o) => ({ ...o, zooId: o.zooId ?? "player" }));
|
|
if ((data.worldZoos === null || data.worldZoos === undefined) && GameConfig.WorldMap && GameConfig.WorldMap.Zoos) {
|
|
data.worldZoos = [...GameConfig.WorldMap.Zoos];
|
|
}
|
|
if (data.worldZoos !== null && data.worldZoos !== undefined && Array.isArray(data.worldZoos)) {
|
|
applyWorldZoosWeightsAndBots(data);
|
|
}
|
|
if (data.worldZoos === null || data.worldZoos === undefined) {
|
|
data.worldZoos = [{ id: "player", name: "Mon zoo", x: 25, y: 50, animalWeights: defaultAnimalWeights() }];
|
|
}
|
|
}
|
|
|
|
function applyWorldZoosWeightsAndBots(data) {
|
|
const keys = getColorNames();
|
|
data.worldZoos = data.worldZoos.map((z) => ({
|
|
...z,
|
|
animalWeights: z.animalWeights && keys.some((k) => k in (z.animalWeights ?? {}))
|
|
? z.animalWeights
|
|
: normalizeZooWeights(z.animalWeights),
|
|
}));
|
|
data.worldZoos.forEach((zoo) => ensureBotState(zoo, zoo.id === "player"));
|
|
}
|
|
|
|
/** Set data[key] to defaultVal when data[key] is null or undefined. defaultVal may be a function (called for value). */
|
|
function setScalarDefault(data, key, defaultVal) {
|
|
if (data[key] === null || data[key] === undefined) {
|
|
data[key] = typeof defaultVal === "function" ? defaultVal() : defaultVal;
|
|
}
|
|
}
|
|
|
|
const LOAD_STATE_SCALAR_DEFAULTS = [
|
|
["worldTruckSales", []],
|
|
["lastEvolutionAt", () => Math.floor(Date.now() / 1000)],
|
|
["nextTokenId", 1],
|
|
["prestigeLevel", 0],
|
|
["timeOfDay", 6],
|
|
["gameDayTotal", 0],
|
|
["lastSeason", "spring"],
|
|
["weather", "sun"],
|
|
["lastWeatherChangeAt", 0],
|
|
["quests", []],
|
|
["lastQuestDay", ""],
|
|
["stats", { eggsPlaced: 0, animalsSold: 0, conveyorUpgrades: 0, plotUpgrades: 0, truckUpgrades: 0, coinsEarned: 0 }],
|
|
["truckLevel", 1],
|
|
["mapZoom", 1],
|
|
["mapPanX", 0],
|
|
["mapPanY", 0],
|
|
["worldMapLevel", 1],
|
|
["researchPoints", 0],
|
|
["pendingBabies", []],
|
|
["receptionAnimals", []],
|
|
["saleListings", []],
|
|
["deathCountRecent", 0],
|
|
["birthCount", 0],
|
|
["reproductionTimers", []],
|
|
["visitorArrivals", []],
|
|
];
|
|
|
|
function applyLoadStateScalarDefaults(data) {
|
|
if (data.laboratoryOffer === undefined) data.laboratoryOffer = null;
|
|
for (const [key, defaultVal] of LOAD_STATE_SCALAR_DEFAULTS) {
|
|
setScalarDefault(data, key, defaultVal);
|
|
}
|
|
if (data.version !== GameConfig.StateVersion) data.version = GameConfig.StateVersion;
|
|
data.autoProfilePickerOpen = false;
|
|
data.autoProfilePickerFamily = undefined;
|
|
if (data.attractivityBonusFromIncidents === null || data.attractivityBonusFromIncidents === undefined) {
|
|
data.attractivityBonusFromIncidents = 0;
|
|
}
|
|
}
|
|
|
|
function applyLoadStateLegacyCells(data) {
|
|
if (data.grid.cells["2_1"] === null || data.grid.cells["2_1"] === undefined) data.grid.cells["2_1"] = { kind: "nursery", level: 1 };
|
|
const c21 = data.grid.cells["2_1"];
|
|
if (c21 && (c21.kind === "plotUpgrade" || c21.kind === "worldMapUpgrade")) data.grid.cells["2_1"] = { kind: "nursery", level: 1 };
|
|
const c12 = data.grid.cells["1_2"];
|
|
if (c12 && (c12.kind === "plotUpgrade" || c12.kind === "worldMapUpgrade")) delete data.grid.cells["1_2"];
|
|
}
|
|
|
|
function normalizeOneAnimalCell(cell, now) {
|
|
if (cell.kind !== "animal") return;
|
|
if (cell.id && !LootTables.Animals[cell.id]) cell.id = "c0_r0";
|
|
if (cell.lastVisitedAt === null || cell.lastVisitedAt === undefined) cell.lastVisitedAt = now;
|
|
if (cell.lastFedAt === null || cell.lastFedAt === undefined) cell.lastFedAt = cell.placedAt ?? now;
|
|
}
|
|
|
|
function normalizeOneEggCell(cell) {
|
|
if (cell.kind === "egg" && cell.eggType && !LootTables.EggTypes[cell.eggType]) cell.eggType = "Color_1";
|
|
}
|
|
|
|
function normalizeLoadedCells(cells) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
for (const key of Object.keys(cells)) {
|
|
const cell = cells[key];
|
|
if (cell) {
|
|
if (cell.kind === "animal") normalizeOneAnimalCell(cell, now);
|
|
if (cell.kind === "egg") normalizeOneEggCell(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
function ensureSchoolCell(data) {
|
|
const hasSchool = Object.values(data.grid.cells).some((c) => c && c.kind === "school");
|
|
if (!hasSchool && !data.grid.cells["1_1"] && (data.conveyorLevel || 0) >= 1) {
|
|
data.grid.cells["1_1"] = { kind: "school", level: data.conveyorLevel || 1 };
|
|
}
|
|
}
|