Initial commit
**Motivations:** - Initialisation du versionning git pour le projet **Root causes:** - N/A (Nouveau projet) **Correctifs:** - N/A **Evolutions:** - Structure initiale du projet - Ajout du .gitignore **Pages affectées:** - Tous les fichiers
This commit is contained in:
213
web/js/conveyor.js
Normal file
213
web/js/conveyor.js
Normal file
@@ -0,0 +1,213 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 eggDef = LootTables.EggTypes[eggType];
|
||||
const minLevel = eggDef ? eggDef.minConveyorLevel : 1;
|
||||
const playerWeights = getPlayerZooWeights(state);
|
||||
const entries = [];
|
||||
for (const zoo of zoos) {
|
||||
const skillLevel = zoo.id === "player" ? getSkillLevel(state) : getZooSkillLevel(zoo);
|
||||
if (skillLevel >= minLevel) {
|
||||
const weights = zoo.id === "player" ? playerWeights : (zoo.animalWeights ?? {});
|
||||
const w = weights[eggType] ?? 0;
|
||||
if (w > 0) entries.push({ id: zoo.id, weight: w });
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user