- 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.
147 lines
6.2 KiB
JavaScript
147 lines
6.2 KiB
JavaScript
// Shared formatting helpers for the modal panels.
|
||
|
||
import { groupDigits } from "../../../shared/text_format.js";
|
||
import { policyById } from "../../../shared/data.js";
|
||
import { marketPriceIndex, resourceMarketValue } from "../../../shared/resources.js";
|
||
import { currencySymbol } from "../currency.js";
|
||
|
||
// The world market's current prices, from the snapshot's market listing.
|
||
export function marketPricesFrom(snapshot) {
|
||
const prices = {};
|
||
for (const entry of (snapshot && snapshot.resourceMarket) || []) {
|
||
prices[entry.id] = entry.price;
|
||
}
|
||
return prices;
|
||
}
|
||
|
||
// A fixed money cost quoted at today's market: a cost with no material bill of
|
||
// its own is scaled by the global price index, so every euro figure drifts with
|
||
// commodity prices.
|
||
export function quotedMoney(amount, prices) {
|
||
return Math.round((amount || 0) * marketPriceIndex(prices));
|
||
}
|
||
|
||
// A construction's full price at today's market: the fixed build cost plus the
|
||
// market value of its whole material bill, ignoring anything already in store.
|
||
export function quotedBuildCost(buildCost, materials, prices) {
|
||
return Math.round((buildCost || 0) + resourceMarketValue(materials, prices));
|
||
}
|
||
|
||
export function formatEffect(effect, level) {
|
||
const value = level <= 0 ? 0 : effect.baseValue * Math.pow(level, effect.exponent);
|
||
switch (effect.stat) {
|
||
case "production":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% production`;
|
||
case "budget_income":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% tax income`;
|
||
case "research":
|
||
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} research/h`;
|
||
case "culture":
|
||
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} culture/h`;
|
||
case "infantry_attack":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% infantry attack`;
|
||
case "infantry_defense":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% infantry defense`;
|
||
case "artillery_attack":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% artillery attack`;
|
||
case "construction_speed":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% construction speed`;
|
||
case "rail_speed":
|
||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% railway speed`;
|
||
default:
|
||
return `${effect.stat} ${value.toFixed(2)}`;
|
||
}
|
||
}
|
||
|
||
export function effectsText(effects, level) {
|
||
const parts = effects.map((e) => formatEffect(e, level));
|
||
return parts.length > 0 ? parts.join(" · ") : "No effect";
|
||
}
|
||
|
||
// The effect line for a building: its stat effects, or, for a utility building
|
||
// whose benefit is a rule, a short description of that rule at `level`.
|
||
export function buildingEffectText(proto, level) {
|
||
if (proto.effects && proto.effects.length > 0) return effectsText(proto.effects, level);
|
||
switch (proto.mechanic) {
|
||
case "parallel_training":
|
||
return `${1 + level} parallel training line${level === 0 ? "" : "s"}`;
|
||
case "parallel_building":
|
||
return `${1 + level} parallel building line${level === 0 ? "" : "s"}`;
|
||
case "civilian_shelter": {
|
||
const saved = Math.round((proto.civilianProtectionPerLevel || 0) * level * 100);
|
||
return `−${saved}% civilian bombing losses · very hard to destroy`;
|
||
}
|
||
case "air_defense":
|
||
return "Fires one volley at hostile aircraft over the city";
|
||
case "port_production": {
|
||
const per = (proto.productionPerSeaTilePerLevel || 0) * level;
|
||
const food = (proto.foodPerSeaTilePerLevel || 0) * level * 100;
|
||
const parts = [`+${groupDigits(per)} production per person per adjacent sea tile to the region`];
|
||
if (food > 0) parts.push(`+${food.toFixed(1)}% food output per adjacent sea tile`);
|
||
return parts.join(" · ");
|
||
}
|
||
case "air_immigration":
|
||
return "Immigration from every other city with an airport";
|
||
default:
|
||
return "No effect";
|
||
}
|
||
}
|
||
|
||
export const SOURCE_LABELS = { building: "building", government: "government", technology: "research" };
|
||
|
||
export function signedMoney(value) {
|
||
return `${value < 0 ? "−" : "+"}${currencySymbol()}${groupDigits(Math.round(Math.abs(value)))}/h`;
|
||
}
|
||
|
||
// A signed money amount with no period suffix, for the monthly budget report.
|
||
export function money(value) {
|
||
return `${value < 0 ? "−" : "+"}${currencySymbol()}${groupDigits(Math.round(Math.abs(value)))}`;
|
||
}
|
||
|
||
export function percent(value) {
|
||
return `${value < 0 ? "−" : "+"}${(Math.abs(value) * 100).toFixed(1)}%`;
|
||
}
|
||
|
||
// Renders one news entry as text. Government actions name the specific actors
|
||
// doing them — "French troops", "French scientists" — rather than the nation as
|
||
// a whole, and the victim or target is named in the people's form ("the
|
||
// Vietnamese").
|
||
export function describeNews(entry, civs) {
|
||
const name = (index) => (civs[index] ? civs[index].name : `Nation ${index + 1}`);
|
||
// The attributive form of a nation, for putting before an actor noun. It
|
||
// falls back to the demonym, which doubles as an adjective for most of them;
|
||
// the few person-nouns (New Zealander, New Hampshirite) carry an explicit one.
|
||
const adjective = (index) => {
|
||
const civ = civs[index];
|
||
if (!civ) return `Nation ${index + 1}`;
|
||
return civ.adjective || civ.demonym || civ.name;
|
||
};
|
||
const people = (index) => {
|
||
const civ = civs[index];
|
||
return civ && civ.demonym ? `the ${civ.demonym}` : name(index);
|
||
};
|
||
switch (entry.type) {
|
||
case "war":
|
||
return `${adjective(entry.actor)} leaders have declared war on ${name(entry.target)}`;
|
||
case "capture":
|
||
return `${adjective(entry.to)} troops have captured ${entry.city} from ${people(entry.from)}`;
|
||
case "policy": {
|
||
const policy = policyById(entry.policy);
|
||
const policyName = policy ? policy.name : entry.policy;
|
||
const who =
|
||
civs[entry.ethnicity] && civs[entry.ethnicity].demonym
|
||
? `the ${civs[entry.ethnicity].demonym}`
|
||
: name(entry.ethnicity);
|
||
return `${adjective(entry.civ)} lawmakers have enacted ${policyName} for ${who}`;
|
||
}
|
||
case "technology":
|
||
return entry.repeatable
|
||
? `${adjective(entry.civ)} scientists have improved ${entry.name}`
|
||
: `${adjective(entry.civ)} scientists have discovered ${entry.name}`;
|
||
case "join":
|
||
return `${entry.player} took control of ${name(entry.civ)}`;
|
||
default:
|
||
return entry.type;
|
||
}
|
||
}
|