Files
Battle-for-Tismo/client/js/modals/format.js
T

153 lines
6.5 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.
// Shared formatting helpers for the modal panels.
import { groupDigits } from "../../../shared/text_format.js";
import { policyById, resourceById } 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 "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)}`;
case "tariff": {
const rate = `${(entry.rate * 100).toFixed(1)}%`;
const resource = entry.resource
? (resourceById(entry.resource) ? resourceById(entry.resource).name : entry.resource)
: "all resources";
const kind = entry.kind === "export" ? "export tariff" : "import tariff";
return `${adjective(entry.civ)} leaders set the ${kind} on ${resource} to ${rate}`;
}
default:
return entry.type;
}
}