Files
Battle-for-Tismo/shared/data/resources.js
T
adrien e7ee5dbf0e Added the resource economy, its works and their icons
- Five resources (energy, steel, food, luxury, high-tech) with per-city stores, a world market, delivery and material upkeep.
- Resource-producing tile works built outside cities; every nation opens with the works its economy needs.
- Real game-icons.net art replacing the dummy icons for the new resources and works, with the credits updated.
2026-09-22 05:27:11 +02:00

415 lines
15 KiB
JavaScript

// Resources: the five material groups nations produce, store, trade and
// consume. Everything is expressed in one canonical unit per resource so the
// simulation never has to convert: energy in kWh, steel and food in tonnes,
// luxury in carats and high-tech in whole units. Display formatting converts to
// the friendly unit (MWh, Mt, kt, ...). The market prices and conversion ratios
// here are the main tuning knobs for the whole resource economy; the per-person
// needs and the energy-per-material figures come straight from the roadmap.
// Energy is stored and moved as kWh. These keep the catalogue readable.
export const KWH_PER_MWH = 1_000;
export const KWH_PER_GWH = 1_000_000;
export const KWH_PER_TWH = 1_000_000_000;
// One tonne of any material counts as this much energy on the wire, so material
// deliveries and energy deliveries share one transport-cost formula.
export const ENERGY_PER_MATERIAL_TONNE = 10 * KWH_PER_GWH; // 10 GWh/t
export const RESOURCES = [
{
id: "energy",
name: "Energy",
unit: "kWh",
icon: "icon_electricity.svg",
colour: "#ffd633",
// Mass of one canonical unit, so a delivery leg can charge transport per
// tonne whatever the good is: 10 GWh of energy weighs a tonne.
tonnesPerUnit: 1 / ENERGY_PER_MATERIAL_TONNE,
// Energy is a flow, not a stock: it comes from the grid and is spent the
// same day. Only cities and resource buildings can hold the other four.
storable: false,
},
{
id: "steel",
colour: "#9aa7b8",
name: "Steel",
unit: "t",
icon: "icon_steel.svg",
tonnesPerUnit: 1,
storable: true,
},
{
id: "food",
colour: "#7ed957",
name: "Food",
unit: "t",
icon: "icon_food.svg",
tonnesPerUnit: 1,
storable: true,
},
{
id: "luxury",
colour: "#d98cff",
name: "Luxury",
unit: "ct",
icon: "icon_luxury.svg",
// A carat is 0.2 g, so 200,000 ct weigh a tonne.
tonnesPerUnit: 0.0002 / 1000,
storable: true,
},
{
id: "hightech",
colour: "#5cc8ff",
name: "High-tech",
unit: "u",
icon: "icon_hightech.svg",
// A high-tech unit is a crate of components, about a kilogram.
tonnesPerUnit: 0.001,
storable: true,
},
];
export const RESOURCE_IDS = RESOURCES.map((r) => r.id);
export const RESOURCE_INDEX = Object.fromEntries(RESOURCES.map((r, i) => [r.id, i]));
// The four stockable resources, in catalogue order. Energy is handled as a flow.
export const STORABLE_RESOURCE_IDS = RESOURCES.filter((r) => r.storable).map((r) => r.id);
export function resourceById(id) {
return RESOURCES[RESOURCE_INDEX[id]] || null;
}
// Global market prices, in euro per canonical unit. These are what a city pays
// when it cannot meet a need from its own store or a neighbour. Energy is
// deliberately cheap: the roadmap's energy-per-material ratios are large, so a
// high electric price would make every material's energy bill dwarf its value.
//
// The five figures are the old nominal prices raised roughly seventeen-fold so
// the production-based GDP (what a nation makes times these prices) lands in
// the same range as the money economy it replaced. The scaling is what makes a
// fresh nation's food output alone read as a plausible per-capita GDP rather
// than a few hundred euro; construction, upkeep and imports are priced off the
// same figures.
export const RESOURCE_MARKET_BASE = {
energy: 0.051, // per kWh
steel: 10_200, // per tonne
food: 5_100, // per tonne
luxury: 1_020, // per carat
hightech: 13_600, // per unit
};
// How strongly a day's world-wide imbalance moves the price, and how far it may
// drift from the base. The market is a smoothing device, not a real auction:
// it lets a shortage raise the cost of imports and a glut cheapen them.
export const RESOURCE_MARKET = {
base: RESOURCE_MARKET_BASE,
adjustment: 0.2,
minMultiplier: 0.5,
maxMultiplier: 1.8,
};
// The physical rules of the resource economy. Every per-person figure and every
// energy-per-material ratio below is fixed by the design; the prices above are
// what we tune against them.
export const RESOURCE_RULES = {
// Each person eats 1 kg of food and consumes 1 kg of steel a day. Both are
// canonical tonnes, so 1 kg is 0.001 t.
foodPerPersonPerDay: 0.001,
steelPerPersonPerDay: 0.001,
// Luxuries are rarer: 0.01 carat a person a day.
luxuryPerPersonPerDay: 0.01,
// Cities aim to hold 130% of a month's food before the month begins.
foodMonthlyBuffer: 1.3,
daysPerMonth: 30,
// A city's baseline electric appetite, in kWh per euro of yearly GDP. This is
// the "kWh per euro of GDP" economic metric; the top bar does not show it.
gdpEnergyIntensity: 0.15,
// Energy the material producers consume to make one canonical unit.
energyPerFoodTonne: 1 * KWH_PER_MWH, // 1 MWh per tonne
energyPerSteelTonne: 20 * KWH_PER_MWH, // 20 MWh per tonne
energyPerLuxuryCarat: {
gold_mine: 10 * KWH_PER_MWH, // 10 MWh per carat
diamond_mine: 5 * KWH_PER_MWH, // 5 MWh per carat
},
energyPerHighTechUnit: 100 * KWH_PER_MWH, // 100 MWh per unit
// Science buildings spend one high-tech unit for every research point.
highTechPerResearchPoint: 1,
// Delivering one tonne of material one tile, in kWh of transport energy.
// Roads use trucks, railways trains; without a road a truck burns the
// off-road figure times the tile's movement cost, and sea legs go by ship.
deliveryEnergy: {
truckRoad: 10 * KWH_PER_MWH, // 10 MWh per tonne per tile
truckOffRoad: 50 * KWH_PER_MWH, // 50 MWh per tonne per tile, times cost
train: 1 * KWH_PER_MWH, // 1 MWh per tonne per tile
ship: 50, // 50 kWh per tonne per tile
},
// Steel every construction spends per euro of its money cost. At 1/10000 a
// 100 M building needs 10,000 t of steel.
steelPerBudgetEuro: 1 / 10_000,
// The electricity a tile work draws as it is built, per euro of its money
// cost. It is roughly the power embodied in the steel it spends: steel costs
// 20 MWh a tonne and 1 t per 10,000 euro, so building is powered as well as
// supplied. Tile improvements pay this up front.
constructionEnergyPerBudgetEuro: 2, // kWh per euro
// High-tech a science or advanced building needs to be raised, per level.
constructionHighTechPerLevel: 100,
// Upkeep is materials, not money: every building, unit and improvement wears
// out and the state buys what it needs. Each figure scales with the thing's
// money value -- a building level's cost, a unit's training cost -- so one set
// of coefficients tunes the whole load. `advanced` things (science, air,
// radar, chips, fusion) also need high-tech.
upkeepSteelPerEuro: 5e-7, // t per euro of value per day
upkeepEnergyPerEuro: 1e-3, // kWh per euro of value per day
upkeepHighTechPerEuro: 2e-6, // u per euro of value per day (advanced only)
// Transport is the exception: its build cost is enormous but its upkeep is a
// per-tile wear figure, not a share of that cost.
transportUpkeepSteel: { road: 0.02, railway: 0.1 },
// Combat is the other exception: guns and aircraft are cheap to keep parked
// and expensive to fire. These are the materials one use spends. Air defense
// fires per hostile aircraft pass, artillery per hourly shelling, an aircraft
// per sortie.
combatConsumption: {
airDefenseVolley: { steel: 20, hightech: 5 },
artilleryVolley: { steel: 1, hightech: 0 },
airStrike: { steel: 100, hightech: 20 },
},
// Infantry eat. A unit carries this many soldiers, and each is fed like a
// civilian: double rations in combat, triple while wounded and resting.
// Training or healing a unit draws its soldiers out of the region's people.
unitPopulation: 1_000,
infantryCombatFoodMultiplier: 2,
infantryRestingFoodMultiplier: 3,
// Supply. A unit carries this many days of its own food. At home, or in
// foreign territory with a clear route to an allied city, it tops up from
// that city; cut off, the days run down and the unit starts to starve.
unitSupplyDays: 50,
starvationHpFractionPerDay: 0.1,
// Repairs: damage leaves a materials debt the owner pays down, and only then
// does the structure recover. A razed city level costs what it cost to build.
repair: {
steelPerTileHp: 0.05, // t to restore one hit point of a tile work
tileHpPerDay: 60, // hit points a funded repair restores each day
steelPerDamage: 0.5, // t of debt per point of structure damage
unitSteelPerHp: 0.01, // t to heal one hit point of a ground or naval unit
unitHighTechPerHp: 0.002, // u to heal one hit point of an advanced unit
},
// Fraction of a missing need that turns into a popularity penalty per day,
// and the same for a GDP-per-capita shortfall, so shortages are felt.
shortagePopularityPenalty: 2,
shortageGdpPenalty: 0.05,
// Food comes from the land, not from the number of people standing on it:
// every worked tile grows `foodPerTilePerDay` tonnes a day scaled by its
// terrain, so moving people between regions changes only income per head.
foodPerTilePerDay: 100,
// How fertile each terrain is. A worked tile grows its base output scaled by
// this, so forests and plains feed cities while deserts and ice do not.
tileFoodFactor: {
Forest: 1.4,
Hills: 0.7,
Land: 1.2,
Desert: 0.1,
Mountain: 0.2,
Tundra: 0.2,
Ice: 0,
},
};
// How far a city will look for a neighbour's surplus before falling back to the
// global market.
export const RESOURCE_TRADE_RADIUS = 8;
// Resource-producing buildings. Built only outside cities, they occupy a land
// tile like a military improvement and are managed through the same tile-works
// queue. A power plant produces energy; every other building consumes energy
// and produces its material. `outputPerDay` is in the resource's canonical
// unit, `energyPerDay` in kWh (positive means consumed, negative produced).
export const RESOURCE_BUILDINGS = [
{
id: "coal_power_plant",
name: "Coal power plant",
description:
"Burns coal to make 250 GWh of electricity a day. Cheap to raise, but the fuel bill never stops.",
resource: "energy",
outputPerDay: 250 * KWH_PER_GWH,
fuelEnergyPerDay: 250 * KWH_PER_GWH * 0.25,
energyPerDay: 0,
buildCost: 500_000_000,
maintenance: 20_000,
icon: "icon_coal_power_plant.svg",
},
{
id: "natural_gas_power_plant",
name: "Natural gas power plant",
description:
"The cleanest fossil plant: 300 GWh a day for a higher fuel bill.",
resource: "energy",
outputPerDay: 300 * KWH_PER_GWH,
fuelEnergyPerDay: 300 * KWH_PER_GWH * 0.25,
energyPerDay: 0,
buildCost: 700_000_000,
maintenance: 25_000,
icon: "icon_gas_power_plant.svg",
},
{
id: "oil_power_plant",
name: "Oil power plant",
description: "Burns oil for 270 GWh of electricity a day.",
resource: "energy",
outputPerDay: 270 * KWH_PER_GWH,
fuelEnergyPerDay: 270 * KWH_PER_GWH * 0.25,
energyPerDay: 0,
buildCost: 600_000_000,
maintenance: 22_000,
icon: "icon_oil_power_plant.svg",
},
{
id: "solar_power_plant",
name: "Solar power plant",
description:
"A field of panels producing a steady 30 GWh a day with no fuel and no upkeep.",
resource: "energy",
outputPerDay: 30 * KWH_PER_GWH,
energyPerDay: 0,
renewable: true,
buildCost: 300_000_000,
maintenance: 0,
icon: "icon_solar_power_plant.svg",
},
{
id: "geothermal_plant",
name: "Geothermal plant",
description: "Taps the earth's heat for 60 GWh a day of free electricity.",
resource: "energy",
outputPerDay: 60 * KWH_PER_GWH,
energyPerDay: 0,
renewable: true,
buildCost: 800_000_000,
maintenance: 0,
icon: "icon_geothermal_plant.svg",
},
{
id: "wind_turbines",
name: "Wind turbines",
description: "A hilltop wind farm making 25 GWh a day for free.",
resource: "energy",
outputPerDay: 25 * KWH_PER_GWH,
energyPerDay: 0,
renewable: true,
buildCost: 200_000_000,
maintenance: 0,
icon: "icon_wind_turbines.svg",
},
{
id: "offshore_wind_turbines",
name: "Offshore wind turbines",
description: "Turbines out at sea making 50 GWh a day. Needs a coastal tile.",
resource: "energy",
outputPerDay: 50 * KWH_PER_GWH,
energyPerDay: 0,
renewable: true,
coastal: true,
buildCost: 500_000_000,
maintenance: 0,
icon: "icon_offshore_wind_turbines.svg",
},
{
id: "fusion_power_plant",
advanced: true,
name: "Fusion power plant",
description: "A late-game reactor producing 4 TWh of electricity a day.",
resource: "energy",
outputPerDay: 4_000 * KWH_PER_GWH,
energyPerDay: 0,
renewable: true,
buildCost: 50_000_000_000,
maintenance: 50_000,
// A wonder of the endgame: only a nation that has mastered fusion may raise
// one. Its upkeep is deliberately high but not the raw share of its enormous
// build cost.
requiresTechnology: "fusion_power",
materialUpkeep: { steel: 1_000, energy: 0, hightech: 500 },
icon: "icon_fusion_power_plant.svg",
},
{
id: "steel_mill",
name: "Steel mill",
description:
"Smelts iron into 2000 t of steel a day, spending 20 MWh of energy per tonne.",
resource: "steel",
outputPerDay: 2_000,
energyPerDay: 2_000 * RESOURCE_RULES.energyPerSteelTonne,
buildCost: 800_000_000,
maintenance: 10_000,
icon: "icon_steel_mill.svg",
},
{
id: "gold_mine",
name: "Gold mine",
description: "Produces 20,000 carats of luxury a day, at 10 MWh a carat.",
resource: "luxury",
outputPerDay: 20_000,
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.gold_mine,
buildCost: 400_000_000,
maintenance: 5_000,
icon: "icon_gold_mine.svg",
},
{
id: "diamond_mine",
name: "Diamond mine",
description: "Produces 20,000 carats of luxury a day, at 5 MWh a carat.",
resource: "luxury",
outputPerDay: 20_000,
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.diamond_mine,
buildCost: 500_000_000,
maintenance: 5_000,
icon: "icon_diamond_mine.svg",
},
{
id: "chip_foundry",
advanced: true,
name: "Chip foundry",
description:
"Fabricates 5000 units of high-tech a day, spending 100 MWh of energy per unit.",
resource: "hightech",
outputPerDay: 5_000,
energyPerDay: 5_000 * RESOURCE_RULES.energyPerHighTechUnit,
buildCost: 2_000_000_000,
maintenance: 10_000,
icon: "icon_chip_foundry.svg",
},
];
// One work stands for a whole industry: its output is scaled up so a nation
// needs far fewer of them on the map. The energy and fuel it draws scale with
// the output too, so a converter stays exactly as efficient as it was authored.
export const RESOURCE_BUILDING_OUTPUT_SCALE = 10;
for (const building of RESOURCE_BUILDINGS) {
building.outputPerDay = (building.outputPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE;
building.energyPerDay = (building.energyPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE;
building.fuelEnergyPerDay = (building.fuelEnergyPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE;
}
// The resource buildings decay in battle like the military improvements do.
for (const building of RESOURCE_BUILDINGS) {
if (building.takesCombatDamage === undefined) building.takesCombatDamage = true;
}
export const RESOURCE_BUILDING_BY_ID = Object.fromEntries(
RESOURCE_BUILDINGS.map((building) => [building.id, building])
);
export function resourceBuildingById(id) {
return RESOURCE_BUILDING_BY_ID[id] || null;
}