Corrected the catalogue descriptions and quoted every rate per day
This commit is contained in:
@@ -78,6 +78,18 @@ There is no linter, formatter, or CI. Do not invent commands beyond these.
|
||||
fragmented and large frames (`server/websocket.js`). Do not lower the buffer
|
||||
limits without checking the snapshot path.
|
||||
- Indent `.js` files with 2 spaces. `.editorconfig` only enforces UTF-8.
|
||||
- The simulation ticks hourly, but the player reads rates per day: money income
|
||||
and upkeep, research and culture effects, campaign spending and the opinion
|
||||
drift are all scaled by `HOURS_PER_DAY` where they are shown
|
||||
(`client/js/modals/format.js` `signedMoney`/`effectNodes`, `budget.js`,
|
||||
`nation.js`, and the approval tooltip in `game_screen/panels.js`). Write new
|
||||
rate copy per day, and leave durations (build time, cooldowns, endurance) in
|
||||
hours.
|
||||
- `description` fields under `shared/data/` are player-facing and must match the
|
||||
simulation: they surface in tooltips — city building cards and resource
|
||||
producers (`client/js/ui/card.js` `tooltipSections`), spy operations and
|
||||
minority policies (`game_screen/panels.js`, `modals/nation.js`) — so correct
|
||||
them when the behaviour they describe changes.
|
||||
- Keep aircraft out of the melee: `_battleTiles`/`_combatTarget` skip them and
|
||||
their strikes are routed explicitly. Mirror the air rules (airport-only
|
||||
rebasing, strike radius, ferry fuel) on both client and server, or prediction
|
||||
|
||||
@@ -1106,6 +1106,7 @@ input:focus, select:focus { border-color: var(--accent); }
|
||||
.card-tooltip .tip-consume,
|
||||
.card-tooltip .tip-cost { color: #e2c08a; }
|
||||
.card-tooltip .tip-none { color: var(--muted); }
|
||||
.card-tooltip .tip-desc { color: var(--text); line-height: 1.35; }
|
||||
.card-tooltip .res { display: inline; white-space: nowrap; }
|
||||
.card-tooltip .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
|
||||
|
||||
@@ -2107,6 +2108,7 @@ input:focus, select:focus { border-color: var(--accent); }
|
||||
}
|
||||
.politics-estimate { margin-top: 8px; font-size: 13px; }
|
||||
.politics-cost { color: #f0b060; }
|
||||
.politics-desc { color: var(--muted); font-size: 12px; }
|
||||
.politics-wizard .politics-controls { margin-top: 0; }
|
||||
.ethnic-trend { font-weight: 700; margin-left: 2px; }
|
||||
.trend-up { color: #6ee787; }
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
// Generated by scripts/generate-devlog.js from `git log`; do not edit.
|
||||
// The pre-commit hook refreshes it so the main menu shows the latest commits.
|
||||
export const DEVLOG = [
|
||||
{
|
||||
"hash": "26e5141",
|
||||
"date": "2026-09-25",
|
||||
"subject": "Split the resource tooltip into aligned production and consumption lines",
|
||||
"body": ""
|
||||
},
|
||||
{
|
||||
"hash": "0a1c2d0",
|
||||
"date": "2026-09-25",
|
||||
@@ -54,11 +60,5 @@ export const DEVLOG = [
|
||||
"date": "2026-09-24",
|
||||
"subject": "Gave the world spies, treaties and nuclear weapons",
|
||||
"body": ""
|
||||
},
|
||||
{
|
||||
"hash": "54efd66",
|
||||
"date": "2026-09-24",
|
||||
"subject": "Grouped the unit stack by type and let ranged units pick a target",
|
||||
"body": ""
|
||||
}
|
||||
];
|
||||
|
||||
@@ -448,7 +448,7 @@ export class GameScreen {
|
||||
!this._isFriendlyAirport(coords)) {
|
||||
this.confirmModal.show(
|
||||
"No runway",
|
||||
"Aircraft may only fly to one of your cities with an airport, or attack an enemy unit.",
|
||||
"Aircraft may only fly to one of your cities with an airport, or strike a hostile unit or city.",
|
||||
null
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ECONOMY,
|
||||
EFFECT_PRODUCTION,
|
||||
APPROVAL,
|
||||
POPULARITY,
|
||||
effectValue,
|
||||
pillageSteps,
|
||||
ROADS,
|
||||
@@ -47,7 +48,7 @@ import {
|
||||
import { defenseFactors, unitDefense, productionFactors, productionMultiplier, popularityDisplay } from "../../../shared/rules.js";
|
||||
import { formatResourceAmount, foodNeedPerDay, protoUpkeep, tileImprovementResourceCost } from "../../../shared/resources.js";
|
||||
import { dateString, hoursInNextMonth } from "../../../shared/game_clock.js";
|
||||
import { DEFAULT_UNIT_SPEED } from "../../../shared/game_state/constants.js";
|
||||
import { DEFAULT_UNIT_SPEED, HOURS_PER_DAY } from "../../../shared/game_state/constants.js";
|
||||
import { flightDistanceTiles } from "../../../shared/hex.js";
|
||||
import { ethnicityChart } from "../ui/ethnicity.js";
|
||||
import { isEconomicMode } from "../map_view/economic.js";
|
||||
@@ -518,19 +519,13 @@ export const panelMethods = {
|
||||
let hint = "Not yet available";
|
||||
if (operation.id === SPY_SABOTAGE) {
|
||||
enabled = this._canSabotage(units);
|
||||
hint = enabled
|
||||
? "Cripple this tile's production for a week (spends the spies)"
|
||||
: "Move the spy onto foreign land";
|
||||
hint = enabled ? operation.description : "Move the spy onto foreign land";
|
||||
} else if (operation.id === SPY_CITY) {
|
||||
enabled = this._spyActionTarget(SPY_CITY, units);
|
||||
hint = enabled
|
||||
? "Read a neighbouring foreign city (spends the spies)"
|
||||
: "Move the spy beside a foreign city";
|
||||
hint = enabled ? operation.description : "Move the spy beside a foreign city";
|
||||
} else if (operation.id === SPY_COMMS) {
|
||||
enabled = this._spyActionTarget(SPY_COMMS, units);
|
||||
hint = enabled
|
||||
? "Intercept a neighbouring capital's messages (spends the spies)"
|
||||
: "Move the spy beside a foreign capital";
|
||||
hint = enabled ? operation.description : "Move the spy beside a foreign capital";
|
||||
}
|
||||
const active = armed === operation.id;
|
||||
$button
|
||||
@@ -1001,15 +996,17 @@ export const panelMethods = {
|
||||
const popularityPoints = stats.popularity || 0;
|
||||
const popularity = popularityDisplay(popularityPoints);
|
||||
this._updateApprovalStat(popularity);
|
||||
// The people drift one point an hour back to neutral, so the estimate is
|
||||
// simply how many points away they are; zero is already there.
|
||||
// The people drift `POPULARITY.driftPerHour` points back to neutral every
|
||||
// hour, so the estimate is how many points away they are spread over that
|
||||
// daily rate; zero is already there.
|
||||
const driftPerDay = POPULARITY.driftPerHour * HOURS_PER_DAY;
|
||||
const pointsAway = Math.abs(popularityPoints);
|
||||
this._setStatTooltip(
|
||||
"stat-approval",
|
||||
pointsAway <= 0
|
||||
? "Popularity is exactly neutral"
|
||||
: `Neutrality in about ${pointsAway} h (~${Math.ceil(pointsAway / 24)} d) ` +
|
||||
"at the current drift of one point an hour"
|
||||
: `Neutrality in about ${Math.ceil(pointsAway / driftPerDay)} d ` +
|
||||
`at the current drift of ${driftPerDay} points a day`
|
||||
);
|
||||
// Popularity doubles its arrow past ten percentage points.
|
||||
this._updateStatArrow(
|
||||
@@ -1677,7 +1674,7 @@ export const panelMethods = {
|
||||
$el.append(
|
||||
$("<div class='site-material-label stalled'></div>").text(
|
||||
"The market is above the budgeted prices. " +
|
||||
"The budget rises 1% a day, doubling each day, until the materials can be bought."
|
||||
"The budget is raised to meet them, then the materials are bought."
|
||||
)
|
||||
);
|
||||
} else {
|
||||
@@ -1829,7 +1826,11 @@ export const panelMethods = {
|
||||
const materials = tileImprovementResourceCost(improvement);
|
||||
const bill = this._resourceItems(materials);
|
||||
const price = quotedBuildCost(improvement.buildCost, materials, prices);
|
||||
// A resource producer's reference text sits above its output; the military
|
||||
// works carry theirs as the produce line.
|
||||
const desc = improvement.resource ? improvement.description || "" : "";
|
||||
return tooltipSections({
|
||||
desc,
|
||||
produce,
|
||||
produceSuffix: improvement.resource ? "/day" : "",
|
||||
consume: this._improvementUpkeepItems(improvement),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// The budget table shared by the nation and city panels. Every row is an item
|
||||
// and its hourly amount, income in green and every cost in red, with the
|
||||
// and its daily amount, income in green and every cost in red, with the
|
||||
// projected total on the last row. Building types are rows that expand to their
|
||||
// per-city cost. `breakdown` mirrors the shape both the national and the
|
||||
// regional economy use, so one renderer serves them both.
|
||||
@@ -88,7 +88,8 @@ export function renderBudget($list, breakdown = {}, { expanded = new Set(), onTo
|
||||
}
|
||||
|
||||
// What the nation spent buying each resource, and earned selling it to other
|
||||
// countries, over the last day, shown as an hourly average.
|
||||
// countries, over the last day, shown as a daily figure. The record holds the
|
||||
// day's total, so it is divided back to an hourly rate for `signedMoney`.
|
||||
if (resourceMoney.length > 0) {
|
||||
$body.append(sectionRow("Resource trade"));
|
||||
for (const entry of resourceMoney) {
|
||||
@@ -103,7 +104,7 @@ export function renderBudget($list, breakdown = {}, { expanded = new Set(), onTo
|
||||
}
|
||||
}
|
||||
|
||||
// The real resource bills of the last day, as hourly averages, in their own
|
||||
// The real resource bills of the last day, as daily figures, in their own
|
||||
// section so they are not confused with the forecast upkeep rows above.
|
||||
const resourceCosts = breakdown.resourceCosts || [];
|
||||
if (resourceCosts.length > 0) {
|
||||
|
||||
@@ -138,7 +138,7 @@ export class CityModal {
|
||||
);
|
||||
for (const row of this.buildingRows) this.$buildingGrid.append(row.card.$card);
|
||||
this.refreshBuildings(view.budget || 0, view.levels || {}, this.gdp, {});
|
||||
this.$budgetSub.text(`Region net +${currencySymbol()}0/h`);
|
||||
this.$budgetSub.text(`Region net +${currencySymbol()}0/day`);
|
||||
this.renderBudget(null);
|
||||
this.renderPeople(
|
||||
view.ethnicMakeup || [],
|
||||
@@ -804,6 +804,7 @@ export class CityModal {
|
||||
const bill = this._resourceItems(materials);
|
||||
const price = this._quotedCost(cost, row.proto, row.level + row.pending);
|
||||
return tooltipSections({
|
||||
desc: row.proto.description || "",
|
||||
produce: effect && effect !== "No effect" ? [{ node: buildingEffectNodes(row.proto, shownLevel) }] : [],
|
||||
consume: this._resourceItems(upkeep),
|
||||
consumeSuffix: "/day",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Shared formatting helpers for the modal panels.
|
||||
|
||||
import { groupDigits } from "../../../shared/text_format.js";
|
||||
import { HOURS_PER_DAY } from "../../../shared/game_clock.js";
|
||||
import { policyById, resourceById, treatyById } from "../../../shared/data.js";
|
||||
import { marketPriceIndex, resourceMarketValue } from "../../../shared/resources.js";
|
||||
import { formatMoneySigned, currencySymbol } from "../currency.js";
|
||||
@@ -34,9 +35,9 @@ export function formatEffect(effect, level) {
|
||||
case "production":
|
||||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% production`;
|
||||
case "research":
|
||||
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} research/h`;
|
||||
return `${value >= 0 ? "+" : ""}${(value * HOURS_PER_DAY).toFixed(2)} research/day`;
|
||||
case "culture":
|
||||
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} culture/h`;
|
||||
return `${value >= 0 ? "+" : ""}${(value * HOURS_PER_DAY).toFixed(2)} culture/day`;
|
||||
case "infantry_attack":
|
||||
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% infantry attack`;
|
||||
case "infantry_defense":
|
||||
@@ -81,7 +82,7 @@ export function buildingEffectText(proto, level) {
|
||||
return `−${saved}% civilian bombing losses · very hard to destroy`;
|
||||
}
|
||||
case "air_defense":
|
||||
return "Fires one volley at hostile aircraft over the city";
|
||||
return "Fires on hostile aircraft and missiles within two tiles";
|
||||
case "port_production": {
|
||||
const per = (proto.productionPerSeaTilePerLevel || 0) * level;
|
||||
const food = (proto.foodPerSeaTilePerLevel || 0) * level * 100;
|
||||
@@ -104,8 +105,8 @@ export function effectNodes(effects, level) {
|
||||
if (index > 0) $frag.append(document.createTextNode(" · "));
|
||||
if (effect.stat === "research") {
|
||||
const value = level <= 0 ? 0 : effect.baseValue * Math.pow(level, effect.exponent);
|
||||
$frag.append(researchNode(value, { sign: value >= 0 ? "+" : "", decimals: 2 }));
|
||||
$frag.append(document.createTextNode("/h"));
|
||||
$frag.append(researchNode(value * HOURS_PER_DAY, { sign: value >= 0 ? "+" : "", decimals: 2 }));
|
||||
$frag.append(document.createTextNode("/day"));
|
||||
} else {
|
||||
$frag.append(document.createTextNode(formatEffect(effect, level)));
|
||||
}
|
||||
@@ -122,8 +123,9 @@ export function buildingEffectNodes(proto, level) {
|
||||
|
||||
export const SOURCE_LABELS = { building: "building", government: "government", technology: "research" };
|
||||
|
||||
// A signed money rate, given per hour by the model and shown per day.
|
||||
export function signedMoney(value) {
|
||||
return `${formatMoneySigned(value)}/h`;
|
||||
return `${formatMoneySigned(value * HOURS_PER_DAY)}/day`;
|
||||
}
|
||||
|
||||
// A signed money amount with no period suffix, for the monthly budget report.
|
||||
|
||||
@@ -1600,8 +1600,8 @@ export class NationModal {
|
||||
this.$technologySub
|
||||
.empty()
|
||||
.append(document.createTextNode("Research: "))
|
||||
.append(researchNode(researchRate || 0, { sign: "+", decimals: 1 }))
|
||||
.append(document.createTextNode("/h"));
|
||||
.append(researchNode((researchRate || 0) * HOURS_PER_DAY, { sign: "+", decimals: 1 }))
|
||||
.append(document.createTextNode("/day"));
|
||||
|
||||
const fin = finances || {};
|
||||
this._treasury = budget;
|
||||
@@ -2482,9 +2482,9 @@ export class NationModal {
|
||||
const hourly = campaign.hourlyCost || 0;
|
||||
const activeHours = Math.max(0, (info.totalHours || 0) - (campaign.startedHours || 0));
|
||||
const spent = hourly * activeHours;
|
||||
const days = Math.floor(activeHours / 24);
|
||||
const days = Math.floor(activeHours / HOURS_PER_DAY);
|
||||
return (
|
||||
`${currencySymbol()}${groupDigits(this._quoted(hourly))}/h · ${currencySymbol()}${groupDigits(Math.round(spent))} spent over ` +
|
||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(hourly) * HOURS_PER_DAY))}/day · ${currencySymbol()}${groupDigits(Math.round(spent))} spent over ` +
|
||||
`${days} day${days === 1 ? "" : "s"}`
|
||||
);
|
||||
}
|
||||
@@ -2512,10 +2512,12 @@ export class NationModal {
|
||||
value: campaign.hourlyCost,
|
||||
});
|
||||
const $spend = $("<span class='politics-readout'></span>").text(
|
||||
`${currencySymbol()}${groupDigits(this._quoted(campaign.hourlyCost))}/h`
|
||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
|
||||
);
|
||||
$slider.on("input", () =>
|
||||
$spend.text(`${currencySymbol()}${groupDigits(this._quoted(Number($slider.val())))}/h`)
|
||||
$spend.text(
|
||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(Number($slider.val())) * HOURS_PER_DAY))}/day`
|
||||
)
|
||||
);
|
||||
$slider.on("change", () => {
|
||||
const amount = Number($slider.val());
|
||||
@@ -2523,7 +2525,9 @@ export class NationModal {
|
||||
// Reverting to the last known spend keeps the control honest until
|
||||
// the player confirms the stop.
|
||||
$slider.val(campaign.hourlyCost);
|
||||
$spend.text(`${currencySymbol()}${groupDigits(this._quoted(campaign.hourlyCost))}/h`);
|
||||
$spend.text(
|
||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
|
||||
);
|
||||
this.onConfirm(
|
||||
"Stop this campaign?",
|
||||
`Cutting the spending to ${currencySymbol()}0 will stop the campaign.`,
|
||||
@@ -2563,7 +2567,7 @@ export class NationModal {
|
||||
);
|
||||
$card.append(
|
||||
$("<div class='desc politics-cost'></div>").text(
|
||||
`Costs ${currencySymbol()}${groupDigits(this._quoted(PROPAGANDA.defaultHourlyCost))}/h to begin; ` +
|
||||
`Costs ${currencySymbol()}${groupDigits(Math.round(this._quoted(PROPAGANDA.defaultHourlyCost) * HOURS_PER_DAY))}/day to begin; ` +
|
||||
"adjust the spending on an active campaign to make it land harder or softer."
|
||||
)
|
||||
);
|
||||
@@ -2677,6 +2681,7 @@ export class NationModal {
|
||||
const $eth = this._civSelect(civs, policy.ethnicity);
|
||||
this.$policyType = $type;
|
||||
this.$policyEth = $eth;
|
||||
this.$policyDesc = $("<div class='politics-estimate politics-desc'></div>");
|
||||
this.$policyTarget = $("<div class='politics-estimate'></div>");
|
||||
this.$policyCost = $("<div class='politics-estimate politics-cost'></div>");
|
||||
this.$policyDuplicate = $("<div class='politics-estimate politics-penalty'></div>");
|
||||
@@ -2701,6 +2706,7 @@ export class NationModal {
|
||||
$controls.append($("<div class='politics-field'></div>").append(this.$policyEnact));
|
||||
this.$politicsWizard.append(
|
||||
$controls,
|
||||
this.$policyDesc,
|
||||
this.$policyTarget,
|
||||
this.$policyCost,
|
||||
this.$policyDuplicate
|
||||
@@ -2719,6 +2725,7 @@ export class NationModal {
|
||||
const type = String(this.$policyType.val());
|
||||
const ethnicity = Number(this.$policyEth.val());
|
||||
const proto = policyById(type);
|
||||
this.$policyDesc.text(proto ? proto.description : "");
|
||||
this.$policyTarget.text(proto ? `Target: ${proto.targetEffect}` : "");
|
||||
this.$policyCost.text(proto ? `Political cost: ${proto.politicalCost}` : "");
|
||||
const duplicate = active.some(
|
||||
|
||||
@@ -60,18 +60,22 @@ export function resourceBill(items, { label = "", sign = "−", suffix = "" } =
|
||||
|
||||
// The three tooltip sections from a work's flows. Each entry is a resource
|
||||
// { id, amount } or free text { text }; free text is shown exactly as written.
|
||||
// `desc` is an optional prose line shown first, with no section heading.
|
||||
export function tooltipSections({
|
||||
desc = "",
|
||||
produce = [],
|
||||
produceSuffix = "",
|
||||
consume = [],
|
||||
consumeSuffix = "",
|
||||
cost = [],
|
||||
} = {}) {
|
||||
return [
|
||||
const sections = [
|
||||
{ label: "Produces", cls: "tip-produce", sign: "+", suffix: produceSuffix, items: produce },
|
||||
{ label: "Consumes", cls: "tip-consume", sign: "−", suffix: consumeSuffix, items: consume },
|
||||
{ label: "Costs", cls: "tip-cost", sign: "−", suffix: "", items: cost },
|
||||
];
|
||||
if (desc) sections.unshift({ desc });
|
||||
return sections;
|
||||
}
|
||||
|
||||
let $tooltip = null;
|
||||
@@ -93,6 +97,10 @@ export function showCardTooltip($anchor, sections) {
|
||||
if (!$anchor || !$anchor.length) return;
|
||||
$tooltip = $("<div class='card-tooltip'></div>");
|
||||
for (const section of sections) {
|
||||
if (section.desc) {
|
||||
$tooltip.append($("<div class='tip-section tip-desc'></div>").text(section.desc));
|
||||
continue;
|
||||
}
|
||||
const $section = $("<div class='tip-section'></div>");
|
||||
$section.append($("<div class='tip-head'></div>").text(section.label));
|
||||
if (!section.items || section.items.length === 0) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const BUILDING_MECHANIC = {
|
||||
PARALLEL_BUILDING: "parallel_building",
|
||||
// Cuts civilian deaths from bombing, and is very hard to knock down.
|
||||
CIVILIAN_SHELTER: "civilian_shelter",
|
||||
// Fires one volley at hostile aircraft passing over the city.
|
||||
// Fires on hostile aircraft and missiles that enter a two-tile corridor.
|
||||
AIR_DEFENSE: "air_defense",
|
||||
// Flat production per head for the city's region, per adjacent sea tile.
|
||||
PORT_PRODUCTION: "port_production",
|
||||
@@ -40,7 +40,7 @@ export const BUILDINGS = [
|
||||
{
|
||||
id: "shelters",
|
||||
name: "Wartime shelters",
|
||||
description: "Each level reduces civilian life loss from bombing by 10%. Extremely hard to destroy. Does not protect other buildings.",
|
||||
description: "Each level shelters another 10% of the city's people from bombing, up to all of them. Extremely hard to destroy. Does not protect other buildings.",
|
||||
baseCost: 100_000_000,
|
||||
costAlpha: 2,
|
||||
refundFraction: 0.5,
|
||||
@@ -58,7 +58,7 @@ export const BUILDINGS = [
|
||||
id: "aa_building",
|
||||
advanced: true,
|
||||
name: "Air defense",
|
||||
description: "Damages hostile aircraft that fly above this city.",
|
||||
description: "Fires on hostile aircraft and missiles that come within two tiles of this city.",
|
||||
baseCost: 1_000_000_000,
|
||||
costAlpha: 2,
|
||||
refundFraction: 0.5,
|
||||
|
||||
@@ -7,7 +7,7 @@ export const GOVERNMENTS = [
|
||||
{
|
||||
id: "republic",
|
||||
name: "Republic",
|
||||
description: "Rule by elected representatives. A balanced government that spreads its support across the economy, trade and the sciences.",
|
||||
description: "Rule by elected representatives. A balanced government that spreads its support across the economy and the sciences.",
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.02,
|
||||
effects: [effect(EFFECT_PRODUCTION, 0.05, 1.0), effect(EFFECT_RESEARCH, 1.0, 1.0)],
|
||||
|
||||
@@ -77,7 +77,7 @@ export const TILE_IMPROVEMENTS = [
|
||||
advanced: true,
|
||||
name: "Coastal cannon",
|
||||
description:
|
||||
"Automatically fires on hostile warships within two tiles. Only a coastal tile can carry one.",
|
||||
"Automatically fires on hostile naval units within two tiles. Only a coastal tile can carry one.",
|
||||
buildCost: 120_000_000,
|
||||
maintenance: 2000,
|
||||
range: 2,
|
||||
|
||||
+12
-12
@@ -311,7 +311,7 @@ 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.",
|
||||
"Burns coal to make 2,500 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,
|
||||
@@ -324,7 +324,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
id: "natural_gas_power_plant",
|
||||
name: "Natural gas power plant",
|
||||
description:
|
||||
"The cleanest fossil plant: 300 GWh a day for a higher fuel bill.",
|
||||
"The cleanest fossil plant: 3,000 GWh a day for a higher fuel bill.",
|
||||
resource: "energy",
|
||||
outputPerDay: 300 * KWH_PER_GWH,
|
||||
fuelEnergyPerDay: 300 * KWH_PER_GWH * 0.25,
|
||||
@@ -336,7 +336,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "oil_power_plant",
|
||||
name: "Oil power plant",
|
||||
description: "Burns oil for 270 GWh of electricity a day.",
|
||||
description: "Burns oil for 2,700 GWh of electricity a day.",
|
||||
resource: "energy",
|
||||
outputPerDay: 270 * KWH_PER_GWH,
|
||||
fuelEnergyPerDay: 270 * KWH_PER_GWH * 0.25,
|
||||
@@ -349,7 +349,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
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.",
|
||||
"A field of panels producing a steady 300 GWh a day with no fuel and no upkeep.",
|
||||
resource: "energy",
|
||||
outputPerDay: 30 * KWH_PER_GWH,
|
||||
energyPerDay: 0,
|
||||
@@ -361,7 +361,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "geothermal_plant",
|
||||
name: "Geothermal plant",
|
||||
description: "Taps the earth's heat for 60 GWh a day of free electricity.",
|
||||
description: "Taps the earth's heat for 600 GWh a day of free electricity.",
|
||||
resource: "energy",
|
||||
outputPerDay: 60 * KWH_PER_GWH,
|
||||
energyPerDay: 0,
|
||||
@@ -373,7 +373,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "wind_turbines",
|
||||
name: "Wind turbines",
|
||||
description: "A hilltop wind farm making 25 GWh a day for free.",
|
||||
description: "A hilltop wind farm making 250 GWh a day for free.",
|
||||
resource: "energy",
|
||||
outputPerDay: 25 * KWH_PER_GWH,
|
||||
energyPerDay: 0,
|
||||
@@ -385,7 +385,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "offshore_wind_turbines",
|
||||
name: "Offshore wind turbines",
|
||||
description: "Turbines out at sea making 50 GWh a day. Needs a coastal tile.",
|
||||
description: "Turbines out at sea making 500 GWh a day. Needs a coastal tile.",
|
||||
resource: "energy",
|
||||
outputPerDay: 50 * KWH_PER_GWH,
|
||||
energyPerDay: 0,
|
||||
@@ -399,7 +399,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
id: "fusion_power_plant",
|
||||
advanced: true,
|
||||
name: "Fusion power plant",
|
||||
description: "A late-game reactor producing 4 TWh of electricity a day.",
|
||||
description: "A late-game reactor producing 40 TWh of electricity a day.",
|
||||
resource: "energy",
|
||||
outputPerDay: 4_000 * KWH_PER_GWH,
|
||||
energyPerDay: 0,
|
||||
@@ -417,7 +417,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
id: "steel_mill",
|
||||
name: "Steel mill",
|
||||
description:
|
||||
"Smelts iron into 20,000 t of steel a day, spending 20 MWh of energy per tonne.",
|
||||
"Smelts iron into 200,000 t of steel a day, spending 20 MWh of energy per tonne.",
|
||||
resource: "steel",
|
||||
outputPerDay: 20_000,
|
||||
energyPerDay: 2_000 * RESOURCE_RULES.energyPerSteelTonne,
|
||||
@@ -428,7 +428,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "gold_mine",
|
||||
name: "Gold mine",
|
||||
description: "Produces 20,000 carats of luxury a day, at 10 MWh a carat.",
|
||||
description: "Produces 200,000 carats of luxury a day, at 10 MWh a carat.",
|
||||
resource: "luxury",
|
||||
outputPerDay: 20_000,
|
||||
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.gold_mine,
|
||||
@@ -439,7 +439,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
{
|
||||
id: "diamond_mine",
|
||||
name: "Diamond mine",
|
||||
description: "Produces 20,000 carats of luxury a day, at 5 MWh a carat.",
|
||||
description: "Produces 200,000 carats of luxury a day, at 5 MWh a carat.",
|
||||
resource: "luxury",
|
||||
outputPerDay: 20_000,
|
||||
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.diamond_mine,
|
||||
@@ -452,7 +452,7 @@ export const RESOURCE_BUILDINGS = [
|
||||
advanced: true,
|
||||
name: "Chip foundry",
|
||||
description:
|
||||
"Fabricates 5000 units of high-tech a day, spending 100 MWh of energy per unit.",
|
||||
"Fabricates 50,000 units of high-tech a day, spending 10 MWh of energy per unit.",
|
||||
resource: "hightech",
|
||||
outputPerDay: 5_000,
|
||||
energyPerDay: 5_000 * RESOURCE_RULES.energyPerHighTechUnit,
|
||||
|
||||
@@ -16,7 +16,7 @@ export const STATUSES = [
|
||||
id: STATUS_ENCIRCLED,
|
||||
label: "Encircled",
|
||||
icon: "encirclement.svg",
|
||||
description: "Cut off from every allied city while a hostile unit is adjacent. Loses 10% of its maximum hit points each day, or 2% inside a city.",
|
||||
description: "Cut off: every adjacent land tile is held by a hostile unit and no friendly port supplies it by sea. Loses 10% of its maximum hit points each day, or 2% inside a city.",
|
||||
hpLossPerDay: 0.10,
|
||||
cityHpLossPerDay: 0.02,
|
||||
},
|
||||
@@ -24,7 +24,7 @@ export const STATUSES = [
|
||||
id: STATUS_BESIEGED,
|
||||
label: "Besieged",
|
||||
icon: "encirclement.svg",
|
||||
description: "Cut off from every other allied city while a hostile unit is adjacent.",
|
||||
description: "Cut off: every adjacent land tile is held by a hostile unit and no friendly port supplies it by sea.",
|
||||
},
|
||||
{
|
||||
id: STATUS_BATTLE,
|
||||
|
||||
@@ -74,7 +74,7 @@ export const TECHNOLOGIES = [
|
||||
{
|
||||
id: "space_program",
|
||||
name: "Space Program",
|
||||
description: "Reaching orbit inspires the nation and spins off technology across the economy.",
|
||||
description: "Reaching orbit inspires the nation, lifting its culture and its industry.",
|
||||
theme: "science",
|
||||
cost: 300,
|
||||
prerequisites: ["industrial_automation", "quantum_computing"],
|
||||
|
||||
@@ -26,7 +26,7 @@ export const TREATIES = [
|
||||
id: TREATY_ALLIANCE,
|
||||
name: "Alliance",
|
||||
description:
|
||||
"The signatories cannot go to war with each other; attacking one declares war on all.",
|
||||
"The signatories spare each other's forces; attacking one breaks the pact and draws its allies into the war.",
|
||||
},
|
||||
{
|
||||
id: TREATY_FREE_TRADE,
|
||||
@@ -48,7 +48,7 @@ export const TREATIES = [
|
||||
{
|
||||
id: TREATY_GIVE_RESOURCES,
|
||||
name: "Give resources",
|
||||
description: "Transfers stores of steel, high-tech, luxury, food or energy.",
|
||||
description: "Transfers stores of steel, high-tech, luxury or food.",
|
||||
payload: "resources",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2291,7 +2291,7 @@ export class GameScreenTest extends TestCase {
|
||||
.map((_, el) => env.$(el).text())
|
||||
.get()
|
||||
.join("|");
|
||||
this.assertTrue(labels.includes("doubling"), `the automatic rise is explained: ${labels}`);
|
||||
this.assertTrue(labels.includes("raised to meet them"), `the automatic rise is explained: ${labels}`);
|
||||
this.assertFalse(labels.includes("Increase budget"), `no raise is offered: ${labels}`);
|
||||
this.assertEqual(
|
||||
env.$("#tile-works .site-warning").length,
|
||||
|
||||
+10
-6
@@ -34,7 +34,10 @@ export class CityModalTest extends TestCase {
|
||||
1,
|
||||
"the lab's science output carries the erlenmeyer icon"
|
||||
);
|
||||
this.assertFalse($tip.text().includes("research"), "the unit word is replaced by the icon");
|
||||
this.assertFalse(
|
||||
$tip.find(".tip-produce").text().includes("research"),
|
||||
"the unit word is replaced by the icon"
|
||||
);
|
||||
modal.close();
|
||||
} finally {
|
||||
teardownDom(env);
|
||||
@@ -287,13 +290,13 @@ export class CityModalTest extends TestCase {
|
||||
};
|
||||
modal.show({ id: 1, name: "Paris" }, { budget: 1.0e15, buildings: BUILDINGS, levels: {} });
|
||||
modal.renderBudget(breakdown);
|
||||
this.assertTrue(env.$("#city-budget-sub").text().includes("+¤635/h"), "the region net is shown");
|
||||
this.assertTrue(env.$("#city-budget-sub").text().includes("+¤15 240/day"), "the region net is shown");
|
||||
const text = env.$("#city-budget-list").text();
|
||||
this.assertTrue(text.includes("+¤1 035/h"), "region tax income is green");
|
||||
this.assertTrue(text.includes("+¤24 840/day"), "region tax income is green");
|
||||
this.assertTrue(text.includes("Bank in Paris (building) +5.0%"), "a building bonus is listed");
|
||||
this.assertTrue(text.includes("−¤400/h"), "the building upkeep is listed");
|
||||
this.assertTrue(text.includes("−¤9 600/day"), "the building upkeep is listed");
|
||||
this.assertTrue(text.includes("TOTAL"), "a total row is present");
|
||||
this.assertTrue(text.includes("+¤635/h"), "the total carries the region net");
|
||||
this.assertTrue(text.includes("+¤15 240/day"), "the total carries the region net");
|
||||
this.assertFalse(text.includes("No units"), "unit upkeep is national, not regional");
|
||||
|
||||
env.$("#city-budget-list tr.budget-group .budget-name").click();
|
||||
@@ -664,7 +667,8 @@ export class NationModalTest extends TestCase {
|
||||
1,
|
||||
"the rate carries the erlenmeyer icon"
|
||||
);
|
||||
this.assertTrue($sub.text().includes("2.5"), "the rate is shown");
|
||||
this.assertTrue($sub.text().includes("60.0"), "the per-day rate is shown");
|
||||
this.assertTrue($sub.text().includes("/day"), "the rate is stated per day");
|
||||
this.assertFalse($sub.text().includes("RP"), "no text 'RP' unit is left");
|
||||
modal.close();
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user