Game-Server/GameServer/systems/ResourceSystem.js
2026-03-10 11:20:02 -03:00

147 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Galaxy Strike Online — Resource System
* GDD §5: Metal, Gas, Crystal, Energy Cells, Dark Matter
* Handles production formulas, storage caps, and economy tick integration.
*/
const RESOURCE_TYPES = ['metal', 'gas', 'crystal', 'energyCells', 'darkMatter'];
// GDD §5.1 base rates & storage
const RESOURCE_CONFIG = {
metal: { baseRate: 100, storageBase: 10000, icon: '⚙', color: '#9e9e9e', label: 'Metal' },
gas: { baseRate: 60, storageBase: 5000, icon: '☁', color: '#4fc3f7', label: 'Gas' },
crystal: { baseRate: 40, storageBase: 5000, icon: '💎', color: '#ce93d8', label: 'Crystal' },
energyCells: { baseRate: 200, storageBase: 8000, icon: '⚡', color: '#fff176', label: 'Energy Cells' },
darkMatter: { baseRate: 5, storageBase: 500, icon: '✦', color: '#b39ddb', label: 'Dark Matter' },
};
// Building → resource production mapping (GDD §5.2, §6.2)
const BUILDING_PRODUCTION = {
mining_facility: { metal: { base: 100, perLevel: 0.15 } },
gas_extractor: { gas: { base: 60, perLevel: 0.15 } },
crystal_refinery: { crystal: { base: 40, perLevel: 0.12 } },
power_reactor: { energyCells:{ base: 200, perLevel: 0.20 } },
// Dark matter only from endgame structures
};
// Building → storage bonus
const BUILDING_STORAGE = {
storage_depot: { all: 2000 },
command_center:{ all: 500 },
};
class ResourceSystem {
constructor() {
this.tickIntervalMs = 60000; // 60-second economy tick per GDD §18.2
}
/** Initialise resources for a new player */
initResources(playerData) {
if (playerData.resources) return; // already initialised
playerData.resources = {
metal: 500,
gas: 200,
crystal: 100,
energyCells: 300,
darkMatter: 0,
lastTick: Date.now(),
};
}
/**
* Calculate per-hour production rates from buildings.
* GDD formula: output = base_rate × (1 + 0.15 × level) × research_mult × sector_richness
*/
getProductionRates(playerData) {
const buildings = playerData.buildings || {};
const research = playerData.research?.effects || {};
const rates = { metal: 0, gas: 0, crystal: 0, energyCells: 0, darkMatter: 0 };
for (const [buildId, prod] of Object.entries(BUILDING_PRODUCTION)) {
const bld = buildings[buildId];
if (!bld || bld.level < 1) continue;
for (const [res, cfg] of Object.entries(prod)) {
const researchMult = 1 + ((research[`${res}Bonus`] || research['miningBonus'] || 0) / 100);
rates[res] += Math.floor(cfg.base * (1 + cfg.perLevel * bld.level) * researchMult);
}
}
return rates;
}
/** Calculate storage caps */
getStorageCaps(playerData) {
const buildings = playerData.buildings || {};
const caps = {};
for (const [res, cfg] of Object.entries(RESOURCE_CONFIG)) {
let cap = cfg.storageBase;
for (const [buildId, bonus] of Object.entries(BUILDING_STORAGE)) {
const bld = buildings[buildId];
if (!bld || bld.level < 1) continue;
if (bonus.all) cap += bonus.all * bld.level;
if (bonus[res]) cap += bonus[res] * bld.level;
}
caps[res] = cap;
}
return caps;
}
/**
* Economy tick — called server-side every 60s.
* Returns { produced, capped } summary.
*/
tick(playerData) {
this.initResources(playerData);
const now = Date.now();
const elapsed = (now - (playerData.resources.lastTick || now)) / 3600000; // hours
if (elapsed <= 0) return { produced: {}, capped: {} };
const rates = this.getProductionRates(playerData);
const caps = this.getStorageCaps(playerData);
const res = playerData.resources;
const produced = {};
const capped = {};
for (const r of RESOURCE_TYPES) {
const gain = Math.floor(rates[r] * elapsed);
if (gain <= 0) continue;
produced[r] = gain;
const before = res[r] || 0;
res[r] = Math.min(before + gain, caps[r]);
if (res[r] === caps[r] && before + gain > caps[r]) capped[r] = true;
}
res.lastTick = now;
return { produced, capped };
}
/** Spend resources — throws if insufficient */
spend(playerData, costs) {
this.initResources(playerData);
const res = playerData.resources;
for (const [r, amount] of Object.entries(costs)) {
if ((res[r] || 0) < amount) {
const label = RESOURCE_CONFIG[r]?.label || r;
throw new Error(`Insufficient ${label}: need ${amount}, have ${res[r] || 0}`);
}
}
for (const [r, amount] of Object.entries(costs)) {
res[r] = (res[r] || 0) - amount;
}
}
/** Add resources (from mining, loot, etc.) */
add(playerData, gains) {
this.initResources(playerData);
const caps = this.getStorageCaps(playerData);
const res = playerData.resources;
for (const [r, amount] of Object.entries(gains)) {
res[r] = Math.min((res[r] || 0) + amount, caps[r] || 999999);
}
}
getConfig() { return RESOURCE_CONFIG; }
getTypes() { return RESOURCE_TYPES; }
getBuildingProd() { return BUILDING_PRODUCTION; }
}
module.exports = { ResourceSystem, RESOURCE_CONFIG, RESOURCE_TYPES };