From 587142751404ed045de40c1157f625a4f0b092e2 Mon Sep 17 00:00:00 2001 From: gauvainboiche Date: Tue, 31 Mar 2026 09:40:00 +0200 Subject: [PATCH] fix: Economy is calculated server-side and not browser-side anymore --- config/game.settings.json | 2 +- public/src/main.js | 6 +- server/econTick.js | 48 +++++++++++++++ server/helpers/economy.js | 124 ++++++++++++++++++++++++++++++++++++++ server/index.js | 2 + 5 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 server/econTick.js create mode 100644 server/helpers/economy.js diff --git a/config/game.settings.json b/config/game.settings.json index ac7ac8b..dfe0bd0 100644 --- a/config/game.settings.json +++ b/config/game.settings.json @@ -1,5 +1,5 @@ { - "clickCooldownSeconds": 0, + "clickCooldownSeconds": 10, "databaseWipeoutIntervalSeconds": 21600, "debugModeForTeams": true, "configReloadIntervalSeconds": 30, diff --git a/public/src/main.js b/public/src/main.js index dfeaf19..8eff9f8 100644 --- a/public/src/main.js +++ b/public/src/main.js @@ -7,12 +7,10 @@ import { fetchGridForSeed, fetchAndApplyScores, updateEconomyDisplay, - tickEconScore, loadEconScores, loadVictoryPoints, loadDbInfo, loadElementBonus, - tickElementBonus, refreshFromServer, refreshGridDisplay, loadPlayfieldMask, @@ -61,8 +59,8 @@ function scheduleScorePoll() { clearTimeout(scorePollTimer); scorePollTimer = window.setTimeout(async () => { await fetchAndApplyScores(); - tickEconScore(ECON_TICK_SECONDS); - tickElementBonus(); + await loadEconScores(); + await loadElementBonus(); scheduleScorePoll(); }, ECON_TICK_SECONDS * 1_000); } diff --git a/server/econTick.js b/server/econTick.js new file mode 100644 index 0000000..ee3bfd8 --- /dev/null +++ b/server/econTick.js @@ -0,0 +1,48 @@ +import { getConfig } from "./configLoader.js"; +import { + ensureSeedEpoch, + getGridCells, + addEconScore, + setElementBonus, +} from "./db/gameDb.js"; +import { computeTeamIncome, computeTeamElementBonus } from "./helpers/economy.js"; + +const TICK_SECONDS = 5; + +/** + * Starts the server-side economy tick loop. + * Runs every TICK_SECONDS, reads the current grid from the DB, computes + * income and element bonus for each team, and persists the results. + * This runs independently of any connected browser. + */ +export function startEconTick() { + setInterval(async () => { + try { + const worldSeed = await ensureSeedEpoch(); + const rows = await getGridCells(worldSeed); + const cfg = getConfig(); + + const resourceWorth = cfg.resourceWorth ?? { common: {}, rare: {} }; + const elementWorth = cfg.elementWorth ?? {}; + + // ── Economic score deltas ───────────────────────────────────────────── + const blueIncome = computeTeamIncome("blue", rows, resourceWorth); + const redIncome = computeTeamIncome("red", rows, resourceWorth); + + const blueDelta = blueIncome * TICK_SECONDS; + const redDelta = redIncome * TICK_SECONDS; + + if (blueDelta > 0) await addEconScore(worldSeed, "blue", blueDelta); + if (redDelta > 0) await addEconScore(worldSeed, "red", redDelta); + + // ── Element bonus (overwrites; reflects current grid state) ────────── + const blueBonus = computeTeamElementBonus("blue", rows, elementWorth); + const redBonus = computeTeamElementBonus("red", rows, elementWorth); + + await setElementBonus(worldSeed, "blue", blueBonus); + await setElementBonus(worldSeed, "red", redBonus); + } catch (e) { + console.error("[econ tick]", e.message); + } + }, TICK_SECONDS * 1_000); +} diff --git a/server/helpers/economy.js b/server/helpers/economy.js new file mode 100644 index 0000000..718a729 --- /dev/null +++ b/server/helpers/economy.js @@ -0,0 +1,124 @@ +// Server-side economy helpers — mirrors public/src/economy.js +// Uses raw DB rows instead of a client-side cells Map. + +// ── Label constants (mirrors public/src/planetEconomy.js) ───────────────────── + +const resources = { + common: { + rock: "Roches communes", + wood: "Bois communs", + mineral: "Minérais communs", + stones: "Gemmes communes", + liquid: "Eau salée", + oil: "Fioul brut", + gas: "Gaz communs", + grain: "Céréales", + livestock: "Bétail commun", + fish: "Poissons commun", + plant: "Plantes communes", + goods: "Biens de consommation", + animals: "Animaux domestiques", + science: "Sites archéologiques", + factory: "Usines standards", + acid: "Acides pauvres", + }, + rare: { + rock: "Roches rares", + wood: "Bois renforcés", + mineral: "Minérais rares", + stones: "Gemmes rares", + liquid: "Eau douce", + oil: "Fioul raffiné", + gas: "Gaz nobles", + grain: "Fruits", + livestock: "Bétail raffiné", + fish: "Poissons raffinés", + plant: "Plantes rares", + goods: "Biens de luxe", + animals: "Animaux exotiques", + science: "Artéfacts anciens", + factory: "Usines planétaires", + acid: "Acides riches", + }, +}; + +const elements = { + common: "Matières premières", + petrol: "Hydrocarbures", + food: "Nourriture", + medic: "Médicaments", + science: "Science", + industry: "Industrie", + money: "Finance", + goods: "Biens", +}; + +// ── Pre-built lookup maps ───────────────────────────────────────────────────── + +/** French resource label → { cat: "common"|"rare", key: string } */ +const LABEL_TO_RESOURCE = new Map(); +for (const [cat, entries] of Object.entries(resources)) { + for (const [key, label] of Object.entries(entries)) { + LABEL_TO_RESOURCE.set(label, { cat, key }); + } +} + +/** French element label → config key (e.g. "Matières premières" → "common") */ +const ELEMENT_LABEL_TO_KEY = Object.fromEntries( + Object.entries(elements).map(([key, label]) => [label, key]) +); + +// ── Income computation ──────────────────────────────────────────────────────── + +/** + * Compute total income per second for a team from DB grid rows. + * + * @param {string} team - "blue" or "red" + * @param {Array<{ has_planet: boolean, planet_json: object|null, discovered_by: string }>} rows + * @param {{ common: object, rare: object }} resourceWorth + * @returns {number} credits per second + */ +export function computeTeamIncome(team, rows, resourceWorth) { + let total = 0; + for (const row of rows) { + if (row.discovered_by !== team) continue; + if (!row.has_planet || !row.planet_json) continue; + const { naturalResources } = row.planet_json; + if (!naturalResources) continue; + for (const [label, pct] of Object.entries(naturalResources)) { + const info = LABEL_TO_RESOURCE.get(label); + if (!info) continue; + const worth = resourceWorth?.[info.cat]?.[info.key] ?? 0; + if (worth === 0) continue; + total += (pct / 100) * worth; + } + } + return total; +} + +// ── Element bonus computation ───────────────────────────────────────────────── + +/** + * Compute cumulative element bonus (%) for a team from DB grid rows. + * + * @param {string} team - "blue" or "red" + * @param {Array<{ has_planet: boolean, planet_json: object|null, discovered_by: string }>} rows + * @param {object} elementWorth - { common: 1, petrol: 3, ... } + * @returns {number} bonus in percent + */ +export function computeTeamElementBonus(team, rows, elementWorth) { + let bonus = 0; + for (const row of rows) { + if (row.discovered_by !== team) continue; + if (!row.has_planet || !row.planet_json) continue; + const { production } = row.planet_json; + if (!production) continue; + for (const [elementLabel, pct] of Object.entries(production)) { + const elementKey = ELEMENT_LABEL_TO_KEY[elementLabel] ?? elementLabel; + const worth = elementWorth?.[elementKey] ?? 0; + if (worth === 0) continue; + bonus += (pct / 100) * worth; + } + } + return bonus; +} diff --git a/server/index.js b/server/index.js index 04cca1d..34fbb4b 100644 --- a/server/index.js +++ b/server/index.js @@ -3,6 +3,7 @@ import { loadConfigFile, getConfig } from "./configLoader.js"; import { initGameSchema, ensureSeedEpoch } from "./db/gameDb.js"; import { initUsersSchema } from "./db/usersDb.js"; import app from "./app.js"; +import { startEconTick } from "./econTick.js"; const PORT = Number(process.env.PORT ?? 8080); @@ -39,6 +40,7 @@ async function main() { }); scheduleConfigPoll(); + startEconTick(); } main().catch((e) => {