Files
adrien 3e429ccb64 Gave production buildings their own economy and fed island regions
Production buildings are now independent private-sector agents: they keep what their output sells for, run only to the demand they can actually sell, and spend that cash on steel and high-tech upgrades when their demand grows. Grid power is a paid private flow between plants and their consumers, and regions synthesise the food their own harvest and the reachable market cannot supply.

Island regions no longer famine: a region with no reachable supplier grows its whole shortfall from energy, and grid power now feeds cities and food before the converters, so a famine never waits on a factory.

Bundles the in-progress tree cleanup that was already present in the working tree.
2026-09-22 14:34:14 +02:00

86 lines
3.2 KiB
JavaScript

// Supply: encirclement and siege detection. A tile is cut off when enemy units
// sit on every land tile beside it and it has no working port to bring supplies
// in by sea; a port is itself blockaded when enemy warships hold every sea tile
// around its city.
import { key } from "../hex.js";
import { STATUS_ENCIRCLED, STATUS_BESIEGED } from "../data/statuses.js";
export const statusMethods = {
// Recomputes the status of every unit and city. A unit cut off is encircled;
// a city cut off is besieged. The check is local -- the ring of land tiles
// around the occupant and its own port -- so it never walks the whole map.
_refreshStatuses() {
for (const unit of this.units) {
unit.statuses = this._isEncircled(unit.coords, unit.civ) ? [STATUS_ENCIRCLED] : [];
}
for (const city of this.cities) {
city.statuses = this._isEncircled(city.coords, city.civ) ? [STATUS_BESIEGED] : [];
}
},
// Whether a hostile unit stands next to `coords`. Kept as the cheap trigger
// for callers that only care whether a fight is near.
_hasAdjacentHostile(coords, civ) {
for (const neighbour of this._neighbours(coords)) {
if (this.unitsAt(neighbour).some((unit) => unit.civ !== civ)) return true;
}
return false;
},
// A tile is encircled when no enemy warship can break the ring: it has no
// working port, and every land tile beside it is held by an enemy unit. A
// tile with no land neighbours has no ring to close, so it is not encircled.
_isEncircled(coords, civ) {
if (this._hasSeaSupply(coords, civ)) return false;
return this._landRingBlocked(coords, civ);
},
_landRingBlocked(coords, civ) {
let land = 0;
for (const neighbour of this._neighbours(coords)) {
const tile = this.tiles[key(neighbour.x, neighbour.y)];
if (!tile || tile.terrainClass !== "Land") continue;
land += 1;
if (!this.unitsAt(neighbour).some((unit) => unit.civ !== civ)) return false;
}
return land > 0;
},
// A friendly city's port supplies its own tile by sea, unless the port is
// blockaded.
_hasSeaSupply(coords, civ) {
const city = this.cityAt(coords);
if (!city || city.civ !== civ) return false;
if (!this.hasCityBuilding(city, "port")) return false;
return !this._isPortBlockaded(city);
},
// A port is blockaded when enemy warships hold every sea tile around the city.
_isPortBlockaded(city) {
let sea = 0;
let held = 0;
for (const neighbour of this._neighbours(city.coords)) {
const tile = this.tiles[key(neighbour.x, neighbour.y)];
if (!tile || tile.terrainClass !== "Sea") continue;
sea += 1;
if (this.unitsAt(neighbour).some(
(unit) => unit.civ !== city.civ && this._isNaval(unit))) {
held += 1;
}
}
return sea > 0 && held === sea;
},
_isNaval(unit) {
const proto = this.unitProto(unit);
// Aircraft may fly over the sea, but only a hull counts as naval supply.
return !!(proto && !proto.air && (proto.traversableTerrains || []).includes("Sea"));
},
_statusHpLossPerDay(def, inCity) {
if (inCity && typeof def.cityHpLossPerDay === "number") return def.cityHpLossPerDay;
return typeof def.hpLossPerDay === "number" ? def.hpLossPerDay : 0;
},
};