Sped up simple construction and showed resource costs on build cards
This commit is contained in:
@@ -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": "4e11af2",
|
||||
"date": "2026-09-25",
|
||||
"subject": "Showed the nation's resource pool in the simple economy's top panel",
|
||||
"body": "The top bar now carries a two-by-two grid of the four storable goods, each a big icon, the grouped pool and an arrow reading production against consumption (purchases excluded); hovering a chip names both daily figures. The quantity column reserves room for 99 999 999 999 so the layout never shifts as stocks grow.\n\nformatResourceAmount paired each prefix with a divisor a thousand times too large, so a million tonnes printed as 1 kt; the steps now move together as kt, Mt, Gt and Tt."
|
||||
},
|
||||
{
|
||||
"hash": "d5305da",
|
||||
"date": "2026-09-25",
|
||||
@@ -54,11 +60,5 @@ export const DEVLOG = [
|
||||
"date": "2026-09-23",
|
||||
"subject": "Founded every nation with a standing army and a year of reserves",
|
||||
"body": ""
|
||||
},
|
||||
{
|
||||
"hash": "0d1995c",
|
||||
"date": "2026-09-23",
|
||||
"subject": "Stopped a tile-work rebuild from discarding every cached trade walk",
|
||||
"body": "The trade graph's cache stamp mixed in the tile-improvement version, which a mine, mill or plant rebuilt in place bumps many times a day, so every city's sea-lane flood was thrown away and rebuilt. The stamp now keys on the transport network, the territory and the storage-node set alone: over ten days the rebuilds fall from hundreds to the 45 the world needs, and the daily resource tick from about 700 ms to about 150 ms.\n\nThe snapshot path is memoised against the counters that actually move it -- the per-civ headline population and GDP, the per-city building and per-nation government/research modifiers, the port and food bonuses, the road and railway tile counts and the ethnic make-up rebuild only when their version changes; the flattened visible set is cached alongside explored; and the HUD upkeep sums without building the labelled budget breakdown. DELTA_COLLECTIONS had drifted from the snapshot's key names (it listed gdpBaseline while the state ships productionBaseline, and tileEthnicity while the version is ethnicity), so the static baseline was re-sent as JSON on every broadcast; the collection/version pairs are now explicit.\n\nTogether a broadcast falls from roughly 60 ms to 18 ms and the bytes for fifty of them from 57 MB to 24 MB, and 200 in-game hours simulate in about a third of the time. regionTiles caches the per-city owned lists behind the daily economy walks, and supplier reserves read a region's headcount without deriving its GDP; getCityEconomy itself stays uncached outside a snapshot so a simulation read still reflects a direct population edit at once."
|
||||
}
|
||||
];
|
||||
|
||||
@@ -53,7 +53,7 @@ import { ethnicityChart } from "../ui/ethnicity.js";
|
||||
import { isEconomicMode } from "../map_view/economic.js";
|
||||
import { isWaterTile } from "../map_view/textures.js";
|
||||
import { audio } from "../audio.js";
|
||||
import { currencySymbol, currencyFor, rateToNational, moneyText } from "../currency.js";
|
||||
import { currencySymbol, currencyFor, rateToNational, moneyText, formatMoney } from "../currency.js";
|
||||
import { buildCard, tooltipSections, hideCardTooltip } from "../ui/card.js";
|
||||
|
||||
export const panelMethods = {
|
||||
@@ -1740,7 +1740,6 @@ export const panelMethods = {
|
||||
const locked = !!opts.locked;
|
||||
const gdp = opts.gdp || 0;
|
||||
const buildHours = ECONOMY.buildingHours(gdp, improvement.buildCost) / (1 + (opts.speed || 0));
|
||||
const materials = tileImprovementResourceCost(improvement);
|
||||
const card = buildCard({
|
||||
icon: improvement.icon,
|
||||
name: improvement.name,
|
||||
@@ -1750,7 +1749,6 @@ export const panelMethods = {
|
||||
locked,
|
||||
action: {
|
||||
label: built ? "Rebuild" : "Build",
|
||||
money: opts.disabled ? null : quotedBuildCost(improvement.buildCost, materials, opts.prices),
|
||||
hours: hours(buildHours),
|
||||
disabled: !!opts.disabled,
|
||||
},
|
||||
@@ -1759,7 +1757,7 @@ export const panelMethods = {
|
||||
title: `Remove ${improvement.name}`,
|
||||
enabled: built,
|
||||
},
|
||||
tip: () => this._improvementTip(improvement),
|
||||
tip: () => this._improvementTip(improvement, opts.prices),
|
||||
onAction: opts.onAction,
|
||||
onRemove: opts.onRemove,
|
||||
});
|
||||
@@ -1776,8 +1774,9 @@ export const panelMethods = {
|
||||
},
|
||||
|
||||
// The hover detail for an improvement card: what it yields, what it wears out
|
||||
// each day and the materials its construction bills in.
|
||||
_improvementTip(improvement) {
|
||||
// each day and the materials its construction bills in. The estimated euro
|
||||
// price sits here, not on the button.
|
||||
_improvementTip(improvement, prices = {}) {
|
||||
const produce = [];
|
||||
if (improvement.resource) {
|
||||
produce.push({ id: improvement.resource, amount: resourceBuildingOutputAt(improvement, 1) });
|
||||
@@ -1787,12 +1786,15 @@ export const panelMethods = {
|
||||
} else if (improvement.description) {
|
||||
produce.push({ text: improvement.description });
|
||||
}
|
||||
const materials = tileImprovementResourceCost(improvement);
|
||||
const bill = this._resourceItems(materials);
|
||||
const price = quotedBuildCost(improvement.buildCost, materials, prices);
|
||||
return tooltipSections({
|
||||
produce,
|
||||
produceSuffix: improvement.resource ? "/day" : "",
|
||||
consume: this._improvementUpkeepItems(improvement),
|
||||
consumeSuffix: "/day",
|
||||
cost: this._resourceItems(tileImprovementResourceCost(improvement)),
|
||||
cost: bill.length > 0 ? [{ text: `~${formatMoney(price)}` }, ...bill] : bill,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
+39
-10
@@ -3,7 +3,7 @@
|
||||
// city's production queue, so the train tab shows both kinds of order.
|
||||
|
||||
import { groupDigits, hours } from "../../../shared/text_format.js";
|
||||
import { ECONOMY, BUILDINGS, resourceById, RESOURCE_IDS, RESOURCE_RULES } from "../../../shared/data.js";
|
||||
import { ECONOMY, BUILDINGS, resourceById, RESOURCE_IDS, RESOURCE_RULES, simpleConstructionHours } from "../../../shared/data.js";
|
||||
import { constructionResourceCost, formatResourceAmount, protoUpkeep, unitFoodPerDay } from "../../../shared/resources.js";
|
||||
import {
|
||||
buildingBuildCost,
|
||||
@@ -111,7 +111,12 @@ export class CityModal {
|
||||
this.coastal = !!view.coastal;
|
||||
this.constructionSpeed = view.constructionSpeed || 0;
|
||||
this.economyModel = view.economyModel || this.economyModel || "hard";
|
||||
this.energyAvailable = view.energyAvailable || 0;
|
||||
// The simple economy's clock is set by the region's spare power, which the
|
||||
// view carries inside its resources; the field is honoured too, because
|
||||
// `_updateCityModal` refreshes it from a later snapshot.
|
||||
this.energyAvailable = view.energyAvailable !== undefined
|
||||
? view.energyAvailable
|
||||
: ((view.resources && view.resources.energy) || 0);
|
||||
this.testing = !!view.testing;
|
||||
this.prices = view.prices || {};
|
||||
this.unitProtos = view.unitProtos || {};
|
||||
@@ -451,6 +456,16 @@ export class CityModal {
|
||||
return 1 / (1 + (this.constructionSpeed || 0));
|
||||
}
|
||||
|
||||
// The build time a card quotes. The simple economy keys it to the region's
|
||||
// spare power, the hard economy to GDP; both then shorten with construction
|
||||
// research. Mirrors the server's clock so the card and the queue agree.
|
||||
_displayHours(cost, kind, gdp = this.gdp) {
|
||||
const base = this.economyModel === "simple"
|
||||
? simpleConstructionHours(cost, this.energyAvailable, kind)
|
||||
: (kind === "unit" ? ECONOMY.trainingHours(gdp, cost) : ECONOMY.buildingHours(gdp, cost));
|
||||
return base * this._constructionTimeFactor();
|
||||
}
|
||||
|
||||
// The full price of a construction at today's market: the build cost plus its
|
||||
// material bill at current prices, whether or not the city already holds the
|
||||
// materials.
|
||||
@@ -469,7 +484,7 @@ export class CityModal {
|
||||
if (!proto.cost && proto.cost !== 0) continue;
|
||||
const serverIndex = proto.serverIndex !== undefined ? proto.serverIndex : index;
|
||||
const locked = !!proto.locked;
|
||||
const duration = ECONOMY.trainingHours(gdp, proto.cost) * this._constructionTimeFactor();
|
||||
const duration = this._displayHours(proto.cost, "unit", gdp);
|
||||
signature.push(`${serverIndex}:${locked ? 1 : 0}`);
|
||||
this.protoNames.set(serverIndex, proto.name);
|
||||
const card = buildCard({
|
||||
@@ -482,7 +497,7 @@ export class CityModal {
|
||||
? { label: "Locked", disabled: true }
|
||||
: {
|
||||
label: "Train",
|
||||
money: this._quotedCost(proto.cost, proto, 0),
|
||||
resources: this._resourceItems(constructionResourceCost(proto, 0, proto.cost)),
|
||||
hours: hours(duration),
|
||||
},
|
||||
tip: () => this._unitTip(proto),
|
||||
@@ -552,7 +567,16 @@ export class CityModal {
|
||||
}
|
||||
const cost = this._resourceItems(constructionResourceCost(proto, 0, proto.cost));
|
||||
if (cost.length > 0) {
|
||||
sections.push({ label: "Costs", cls: "tip-cost", sign: "−", suffix: "", items: cost });
|
||||
// The money quote lives here, not on the button, so the button shows only
|
||||
// the material bill it can fit.
|
||||
const price = this._quotedCost(proto.cost, proto, 0);
|
||||
sections.push({
|
||||
label: "Costs",
|
||||
cls: "tip-cost",
|
||||
sign: "−",
|
||||
suffix: "",
|
||||
items: [{ text: `~${formatMoney(price)}` }, ...cost],
|
||||
});
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
@@ -769,18 +793,21 @@ export class CityModal {
|
||||
|
||||
// The hover detail for a building card: what it makes at the level it is
|
||||
// buying, what it wears out each day there, and the materials its next order
|
||||
// spends.
|
||||
// spends. The estimated euro price sits here rather than on the button, whose
|
||||
// meta carries only the material bill and the clock.
|
||||
_buildingTip(row) {
|
||||
const shownLevel = Math.max(row.level + row.pending, 1);
|
||||
const effect = buildingEffectText(row.proto, shownLevel);
|
||||
const upkeep = protoUpkeep(row.proto, buildingBuildCost(row.proto, shownLevel - 1));
|
||||
const cost = buildingBuildCost(row.proto, row.level + row.pending);
|
||||
const materials = constructionResourceCost(row.proto, row.level + row.pending, cost);
|
||||
const bill = this._resourceItems(materials);
|
||||
const price = this._quotedCost(cost, row.proto, row.level + row.pending);
|
||||
return tooltipSections({
|
||||
produce: effect && effect !== "No effect" ? [{ node: buildingEffectNodes(row.proto, shownLevel) }] : [],
|
||||
consume: this._resourceItems(upkeep),
|
||||
consumeSuffix: "/day",
|
||||
cost: this._resourceItems(materials),
|
||||
cost: bill.length > 0 ? [{ text: `~${formatMoney(price)}` }, ...bill] : bill,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -830,10 +857,12 @@ export class CityModal {
|
||||
.text(requirement || levelText);
|
||||
row.card.$action.prop("disabled", !!requirement);
|
||||
const cost = buildingBuildCost(row.proto, nextLevel);
|
||||
const duration = ECONOMY.buildingHours(gdp, cost) * this._constructionTimeFactor();
|
||||
const duration = this._displayHours(cost, "building", gdp);
|
||||
setCardMeta(row.card.$meta, {
|
||||
money: requirement ? null : this._quotedCost(cost, row.proto, nextLevel),
|
||||
hours: hours(duration),
|
||||
resources: requirement
|
||||
? null
|
||||
: this._resourceItems(constructionResourceCost(row.proto, nextLevel, cost)),
|
||||
hours: requirement ? "" : hours(duration),
|
||||
});
|
||||
if (row.card.$remove) {
|
||||
// Downgrading returns nothing, so the control is only enabled once the
|
||||
|
||||
+12
-8
@@ -7,7 +7,6 @@
|
||||
import { resourceById } from "../../../shared/data.js";
|
||||
import { formatResourceAmount } from "../../../shared/resources.js";
|
||||
import { groupDigits } from "../../../shared/text_format.js";
|
||||
import { formatMoney } from "../currency.js";
|
||||
|
||||
// The materials a construction bills in, in reading order.
|
||||
export const CARD_RESOURCES = ["steel", "energy", "hightech"];
|
||||
@@ -122,17 +121,22 @@ export function showCardTooltip($anchor, sections) {
|
||||
}
|
||||
}
|
||||
|
||||
// Puts the action button's price and time in its meta line. The money is an
|
||||
// estimate, so it is italic and prefixed with a tilde; nothing is shown when
|
||||
// the work has no price (locked or already standing). Callers whose cost is not
|
||||
// money pass free `text` instead, shown verbatim.
|
||||
export function setCardMeta($meta, { money = null, hours = "", text = "" } = {}) {
|
||||
// Puts the action button's material cost and build time in its meta line.
|
||||
// `resources` is the work's material bill (an array of { id, amount }), drawn
|
||||
// with the commodity icons and a minus sign; the hours follow it. A work with
|
||||
// nothing to spend (locked, or already standing) shows nothing. Callers whose
|
||||
// cost is not a resource bill pass free `text` instead, shown verbatim. The
|
||||
// estimated euro price lives in the hover tooltip, never here, so a long bill
|
||||
// cannot overflow the button.
|
||||
export function setCardMeta($meta, { resources = null, hours = "", text = "" } = {}) {
|
||||
$meta.empty();
|
||||
if (money != null) {
|
||||
$meta.append($("<span class='bt-price'></span>").text(`~${formatMoney(money)}`));
|
||||
if (resources && resources.length) {
|
||||
$meta.append(resourceBill(resources, { sign: "−" }));
|
||||
if (hours) $meta.append(document.createTextNode(` · ${hours}`));
|
||||
} else if (text) {
|
||||
$meta.text(text);
|
||||
} else if (hours) {
|
||||
$meta.text(hours);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,10 @@ export const SIMPLE_ECONOMY = {
|
||||
// max(available, minEnergy)`, then the building/training ratio below and the
|
||||
// usual approval and technology multipliers. A tighter grid raises the same
|
||||
// work slower, the opposite of the hard economy's richer-nation-builds-faster.
|
||||
energyPerConstructionHour: 3_000,
|
||||
// Tuned so a modern-infantry unit (cost 100M) trains in 45 in-game hours --
|
||||
// 45 real seconds, one hour a second -- in a starter region holding the
|
||||
// typical ~3.5e9 kWh a day of spare power.
|
||||
energyPerConstructionHour: 1_600,
|
||||
// The least spare power a region is credited with, so a blacked-out region
|
||||
// still builds -- slowly -- instead of dividing by zero or finishing at
|
||||
// once. A tenth of a solar plant's daily output.
|
||||
@@ -69,6 +72,25 @@ export const SIMPLE_ECONOMY = {
|
||||
},
|
||||
};
|
||||
|
||||
// The base hours a simple-economy work of `cost` takes at `energy` kWh a day of
|
||||
// spare regional power. `kind` is "unit" for training, anything else for a
|
||||
// building, tile work or upgrade, which the figures in `construction` run
|
||||
// faster. Energy is floored so a blacked-out region still builds -- slowly --
|
||||
// rather than dividing by zero or finishing at once. Shared by the server's
|
||||
// clock and the browser's quoted times, so the two can never disagree.
|
||||
export function simpleConstructionHours(cost, energy, kind) {
|
||||
const available = Math.max(
|
||||
Number.isFinite(energy) ? energy : 0,
|
||||
SIMPLE_ECONOMY.construction.minEnergy
|
||||
);
|
||||
const base = Math.max(0, cost) *
|
||||
SIMPLE_ECONOMY.construction.energyPerConstructionHour / available;
|
||||
const scaled = kind === "unit"
|
||||
? base
|
||||
: base * SIMPLE_ECONOMY.construction.buildingTimeMultiplier;
|
||||
return Math.max(SIMPLE_ECONOMY.construction.minHours, scaled);
|
||||
}
|
||||
|
||||
// The global market every simple-economy nation buys and sells from. One shared
|
||||
// world stock per storable good opens at this figure, and the price is the base
|
||||
// price scaled by how far the stock has fallen from it: half the stock doubles
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
STORABLE_RESOURCE_IDS,
|
||||
RESOURCE_MARKET_BASE,
|
||||
SIMPLE_ECONOMY,
|
||||
simpleConstructionHours,
|
||||
MARKET_START_STOCK,
|
||||
MARKET_LOTS,
|
||||
marketPriceFor,
|
||||
@@ -811,18 +812,10 @@ export const simpleEconomyMethods = {
|
||||
// The base hours a work of `cost` takes in `city`'s region at today's spare
|
||||
// power. `kind` is "unit" for training, anything else for a building, tile
|
||||
// work or upgrade -- which the hard economy runs ten times quicker. The energy
|
||||
// is floored so a blacked-out region still builds, slowly.
|
||||
// is floored so a blacked-out region still builds, slowly. The arithmetic
|
||||
// lives in the data module so the browser quotes the same figure.
|
||||
_simpleConstructionHours(city, cost, kind) {
|
||||
const energy = Math.max(
|
||||
this.regionAvailableEnergy(city),
|
||||
SIMPLE_ECONOMY.construction.minEnergy
|
||||
);
|
||||
const base = Math.max(0, cost) *
|
||||
SIMPLE_ECONOMY.construction.energyPerConstructionHour / energy;
|
||||
const scaled = kind === "unit"
|
||||
? base
|
||||
: base * SIMPLE_ECONOMY.construction.buildingTimeMultiplier;
|
||||
return Math.max(SIMPLE_ECONOMY.construction.minHours, scaled);
|
||||
return simpleConstructionHours(cost, this.regionAvailableEnergy(city), kind);
|
||||
},
|
||||
|
||||
// Pays a construction bill out of the nation's pool. Atomic: a bill the pool
|
||||
|
||||
+16
-10
@@ -2090,8 +2090,8 @@ export class GameScreenTest extends TestCase {
|
||||
this.assertEqual(options.length, 2, "road and railway are listed");
|
||||
this.assertFalse(options.eq(0).prop("disabled"), "the road option is clickable");
|
||||
this.assertFalse(options.eq(1).prop("disabled"), "the railway option is clickable");
|
||||
// Each card shows the work's icon and name, while the resource bill is in
|
||||
// the hover tooltip and the estimated price sits on the button.
|
||||
// Each card shows the work's icon and name; the resource bill and the
|
||||
// estimated price both live in the hover tooltip.
|
||||
const roadCard = options.eq(0).closest(".building-tile");
|
||||
this.assertTrue(
|
||||
roadCard.find("img.building-icon").attr("src").endsWith("icon_road.svg"),
|
||||
@@ -2107,9 +2107,9 @@ export class GameScreenTest extends TestCase {
|
||||
2,
|
||||
"steel and energy carry resource icons"
|
||||
);
|
||||
options.eq(0).trigger("mouseleave");
|
||||
// The money figure is the build cost plus the material bill at today's
|
||||
// market prices, not the raw build cost.
|
||||
// market prices, not the raw build cost. It lives in the tooltip, so the
|
||||
// button's meta stays a compact resource bill.
|
||||
const roadProto = TRANSPORT_IMPROVEMENTS.find((i) => i.id === "road");
|
||||
const expectedCost = quotedBuildCost(
|
||||
roadProto.buildCost,
|
||||
@@ -2117,9 +2117,10 @@ export class GameScreenTest extends TestCase {
|
||||
marketPricesFrom(screen.snapshot)
|
||||
);
|
||||
this.assertTrue(
|
||||
options.eq(0).find(".bt-price").text().includes(groupDigits(Math.round(expectedCost))),
|
||||
`the market price is shown: ${options.eq(0).find(".bt-price").text()}`
|
||||
tipCost.includes(groupDigits(Math.round(expectedCost))),
|
||||
`the market price is shown in the tooltip: ${tipCost}`
|
||||
);
|
||||
options.eq(0).trigger("mouseleave");
|
||||
options.eq(0).click();
|
||||
const order = network.orders[network.orders.length - 1];
|
||||
this.assertEqual(order.type, "build_improvement");
|
||||
@@ -2378,15 +2379,20 @@ export class GameScreenTest extends TestCase {
|
||||
screen.onState(state.snapshot(0));
|
||||
screen._showTile(coords);
|
||||
env.$("#tile-manage").click();
|
||||
const before = env.$("#tile-manage-improvements .transport-btn").eq(0)
|
||||
.find(".bt-price").text();
|
||||
const quotedTip = () => {
|
||||
const $btn = env.$("#tile-manage-improvements .transport-btn").eq(0);
|
||||
$btn.trigger("mouseenter");
|
||||
const text = env.$(".card-tooltip .tip-cost").text();
|
||||
$btn.trigger("mouseleave");
|
||||
return text;
|
||||
};
|
||||
const before = quotedTip();
|
||||
// A jump in the world steel price raises the quoted price, without
|
||||
// changing the amount of steel shown.
|
||||
state.setResourcePrice("steel", state.getResourcePrice("steel") * 3);
|
||||
screen.onState(state.snapshot(0));
|
||||
screen._showTile(coords);
|
||||
const after = env.$("#tile-manage-improvements .transport-btn").eq(0)
|
||||
.find(".bt-price").text();
|
||||
const after = quotedTip();
|
||||
this.assertNotEqual(before, after, `the quote moved: ${before} -> ${after}`);
|
||||
// The steel amount itself is unchanged, in the tooltip.
|
||||
env.$("#tile-manage-improvements .transport-btn").eq(0).trigger("mouseenter");
|
||||
|
||||
@@ -10,6 +10,7 @@ import { GameServer } from "../server/game_server.js";
|
||||
import { key, parseKey } from "../shared/hex.js";
|
||||
import {
|
||||
SIMPLE_ECONOMY,
|
||||
simpleConstructionHours,
|
||||
tileImprovementById,
|
||||
isResourceTileBuilding,
|
||||
} from "../shared/data.js";
|
||||
@@ -133,6 +134,25 @@ export class SimpleConstructionTest extends TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
// The reference calibration: a modern-infantry unit (cost 100M) trains in
|
||||
// about 45 in-game hours -- 45 real seconds -- in a starter region with the
|
||||
// typical spare power. The rate lives in the shared data module so the server
|
||||
// clock and the browser's quoted times derive it the same way.
|
||||
test_the_reference_unit_trains_in_45_hours_in_a_starter_region() {
|
||||
const energy = 3_500_000_000;
|
||||
const hours = simpleConstructionHours(100_000_000, energy, "unit");
|
||||
this.assertApprox(hours, 45, 1, "modern infantry is about 45 hours at starter power");
|
||||
this.assertApprox(
|
||||
simpleConstructionHours(100_000_000, energy, "building") * 10,
|
||||
hours,
|
||||
0.5,
|
||||
"a building of the same cost runs ten times quicker"
|
||||
);
|
||||
const dead = simpleConstructionHours(100_000_000, 0, "unit");
|
||||
this.assertGreater(dead, hours, "a blacked-out region builds slower");
|
||||
this.assertGreaterOrEqual(dead, SIMPLE_ECONOMY.construction.minHours, "but not instantly");
|
||||
}
|
||||
|
||||
test_more_regional_energy_strictly_shortens_the_clock() {
|
||||
const state = simpleState();
|
||||
const city = cityOf(state, 0);
|
||||
|
||||
+49
-2
@@ -2,9 +2,9 @@ import { TestCase } from "./framework/test_case.js";
|
||||
import { setupDom, teardownDom } from "./framework/dom.js";
|
||||
import { CityModal, NationModal } from "../client/js/modals.js";
|
||||
import { panelMethods } from "../client/js/game_screen/panels.js";
|
||||
import { GOVERNMENTS, TECHNOLOGIES } from "../shared/data.js";
|
||||
import { GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS, BUILDINGS } from "../shared/data.js";
|
||||
import { groupDigits } from "../shared/text_format.js";
|
||||
import { formatResourceAmount } from "../shared/resources.js";
|
||||
import { constructionResourceCost, formatResourceAmount } from "../shared/resources.js";
|
||||
import { simpleState, smallState, cityOf } from "./framework/helpers.js";
|
||||
import {
|
||||
setCurrency,
|
||||
@@ -160,6 +160,53 @@ export class SimpleUiTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// The unit and building cards show what a work spends in materials, not only
|
||||
// its money quote: the steel (and high-tech) bill, drawn with the commodity
|
||||
// icons on the action button's meta line.
|
||||
async test_unit_and_building_cards_show_their_resource_cost() {
|
||||
const env = await setupDom();
|
||||
try {
|
||||
const modal = new CityModal();
|
||||
const unit = { ...PROTO_UNITS[0], serverIndex: 0 };
|
||||
modal.show(
|
||||
{ id: 1, name: "Lyon" },
|
||||
{
|
||||
economyModel: "simple",
|
||||
resources: { energy: 3_500_000_000 },
|
||||
gdp: 1.0e9,
|
||||
trainable: [unit],
|
||||
unitProtos: PROTO_UNITS,
|
||||
buildings: BUILDINGS,
|
||||
levels: {},
|
||||
}
|
||||
);
|
||||
const unitBill = constructionResourceCost(PROTO_UNITS[0], 0, PROTO_UNITS[0].cost);
|
||||
const $unitMeta = env.$("#city-train-list .bt-meta").first();
|
||||
this.assertTrue(
|
||||
$unitMeta.find("img[src$='icon_steel.svg']").length === 1,
|
||||
"the unit's steel cost is on the card"
|
||||
);
|
||||
this.assertTrue(
|
||||
$unitMeta.text().includes(formatResourceAmount("steel", unitBill.steel)),
|
||||
`the unit's steel bill is quoted: ${$unitMeta.text()}`
|
||||
);
|
||||
this.assertTrue(
|
||||
$unitMeta.text().includes("45"),
|
||||
`the unit's simple build clock is quoted: ${$unitMeta.text()}`
|
||||
);
|
||||
|
||||
const index = BUILDINGS.findIndex((b) => b.id === "barracks");
|
||||
const $buildMeta = env.$("#city-buildings-list .building-tile").eq(index).find(".bt-meta");
|
||||
this.assertTrue(
|
||||
$buildMeta.find("img[src$='icon_steel.svg']").length === 1,
|
||||
"the building's steel cost is on the card"
|
||||
);
|
||||
modal.close();
|
||||
} finally {
|
||||
teardownDom(env);
|
||||
}
|
||||
}
|
||||
|
||||
test_a_single_currency_book_needs_no_conversion() {
|
||||
setCurrency({ code: "EUR", symbol: "€", name: "Euro" });
|
||||
setCurrencyBook(
|
||||
|
||||
Reference in New Issue
Block a user