Showed the culture a propaganda campaign drains
The simple economy pays for propaganda out of the nation's culture stock and lapses a campaign it can no longer fund, but the tab quoted money and never named the store. Give culture the clapperboard icon as a non-tradeable resource like research, read the stock and the campaigns' daily drain out on the Propaganda tab, and price every campaign figure in culture when the simple economy runs.
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M2 2h508v508H2z" fill="#000000" fill-opacity="1" stroke="#000000" stroke-opacity="1" stroke-width="2"></path><g class="" style="" transform="translate(0,0)"><path d="m419.682 26.2-8.66 2.452L32.915 135.81 55.27 214.7l386.77-109.608L419.683 26.2zm-12.41 22.224 9.074 32.014-41.086-22.942 32.013-9.072zM350.77 64.438l56.8 31.714-37.084 10.51-56.8-31.715 37.084-10.51zm-61.577 17.45 56.803 31.716-37.084 10.51-56.8-31.718 37.08-10.51zm-61.574 17.45 56.802 31.715-37.084 10.51-56.803-31.715 37.084-10.51zm-61.577 17.45 56.803 31.716-37.084 10.51-56.8-31.717 37.08-10.51zm-61.574 17.45 56.8 31.715-37.083 10.51-56.802-31.715 37.084-10.51zm-45.86 26.227 41.085 22.94-32.01 9.072-9.074-32.012zM55 215v274h402V215H55zm18 18h33.273L73 266.273V233zm58.727 0h38.546l-46 46H85.727l46-46zm64 0h38.546l-46 46h-38.546l46-46zm64 0h38.546l-46 46h-38.546l46-46zm64 0h38.546l-46 46h-38.546l46-46zm64 0h38.546l-46 46h-38.546l46-46zM439 245.727V279h-33.273L439 245.727zM73 297h366v174H73V297zm248.635 46.57-192.44.703.067 18 192.44-.703-.067-18zM130.7 391.33l-.134 17.998 92.707.703.137-18-92.71-.7zm127.155.7-.2 18 63.913.702.2-17.998-63.913-.703z" fill="#9013fe" fill-opacity="1"></path></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -2263,6 +2263,9 @@ input:focus, select:focus { border-color: var(--accent); }
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
}
|
}
|
||||||
.politics-readout { color: var(--muted); font-size: 12px; margin-left: 8px; }
|
.politics-readout { color: var(--muted); font-size: 12px; margin-left: 8px; }
|
||||||
|
.politics-card .res { display: inline; white-space: nowrap; }
|
||||||
|
.politics-card .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
|
||||||
|
.culture-readout { margin-left: 0; font-size: 13px; }
|
||||||
.politics-policy {
|
.politics-policy {
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.07);
|
border-top: 1px solid rgba(255, 255, 255, 0.07);
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
// Generated by scripts/generate-devlog.js from `git log`; do not edit.
|
// 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.
|
// The pre-commit hook refreshes it so the main menu shows the latest commits.
|
||||||
export const DEVLOG = [
|
export const DEVLOG = [
|
||||||
|
{
|
||||||
|
"hash": "9fc94a8",
|
||||||
|
"date": "2026-09-28",
|
||||||
|
"subject": "Rebuilt the technology panel as one lane-routed draggable graph",
|
||||||
|
"body": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"hash": "3c737e9",
|
"hash": "3c737e9",
|
||||||
"date": "2026-09-28",
|
"date": "2026-09-28",
|
||||||
@@ -54,11 +60,5 @@ export const DEVLOG = [
|
|||||||
"date": "2026-09-24",
|
"date": "2026-09-24",
|
||||||
"subject": "Made training gather materials, fixed the trade graph, and banked money gifts",
|
"subject": "Made training gather materials, fixed the trade graph, and banked money gifts",
|
||||||
"body": "Unit orders now gather steel and high-tech over the days before training, exactly as buildings do, and clicking a queued order reveals what it is gathering, its reserved budget and the soldiers it will draw.\n\nThe trade graph cached a producer-less node list under a stamp that already reflected the producers' stores, so every seeded mine stayed invisible to buyers and rare-good prices settled at a huge multiple of base; the storage nodes are now re-read after production.\n\nMoney gifts draw the giver's central-bank reserves in the denomination first, buy the rest from the issuing bank at the market rate, convert into the receiver's currency, and cannot buy from a bank their nation is at war with.\n\nPruned the finished design briefs (intelligence, stacks, air movement, politics performance, economy balance, training UI and FIXME) and refreshed the index and cross-references."
|
"body": "Unit orders now gather steel and high-tech over the days before training, exactly as buildings do, and clicking a queued order reveals what it is gathering, its reserved budget and the soldiers it will draw.\n\nThe trade graph cached a producer-less node list under a stamp that already reflected the producers' stores, so every seeded mine stayed invisible to buyers and rare-good prices settled at a huge multiple of base; the storage nodes are now re-read after production.\n\nMoney gifts draw the giver's central-bank reserves in the denomination first, buy the rest from the issuing bank at the market rate, convert into the receiver's currency, and cannot buy from a bank their nation is at war with.\n\nPruned the finished design briefs (intelligence, stacks, air movement, politics performance, economy balance, training UI and FIXME) and refreshed the index and cross-references."
|
||||||
},
|
|
||||||
{
|
|
||||||
"hash": "afb23df",
|
|
||||||
"date": "2026-09-24",
|
|
||||||
"subject": "Cut the migration sweep's repeated lookups and population churn",
|
|
||||||
"body": ""
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2744,6 +2744,10 @@ export const panelMethods = {
|
|||||||
localCiv,
|
localCiv,
|
||||||
totalHours: snapshot.totalHours || 0,
|
totalHours: snapshot.totalHours || 0,
|
||||||
campaigns: snapshot.campaigns || [],
|
campaigns: snapshot.campaigns || [],
|
||||||
|
// The nation's culture stock, which the simple economy spends on the
|
||||||
|
// campaigns, and the model that decides which currency they are paid in.
|
||||||
|
culture: snapshot.culture || [],
|
||||||
|
economyModel: this.economyModel,
|
||||||
opinions: snapshot.opinions || { ethnic: [], government: [] },
|
opinions: snapshot.opinions || { ethnic: [], government: [] },
|
||||||
spies: (snapshot.units || [])
|
spies: (snapshot.units || [])
|
||||||
.filter((unit) => unit.civ === localCiv && this._isSpy(unit))
|
.filter((unit) => unit.civ === localCiv && this._isSpy(unit))
|
||||||
|
|||||||
+106
-28
@@ -16,6 +16,7 @@ import {
|
|||||||
MARKET_LOTS,
|
MARKET_LOTS,
|
||||||
resourceById,
|
resourceById,
|
||||||
PRICE_INDEX_WINDOW_DAYS,
|
PRICE_INDEX_WINDOW_DAYS,
|
||||||
|
SIMPLE_ECONOMY,
|
||||||
TREATIES,
|
TREATIES,
|
||||||
treatyById,
|
treatyById,
|
||||||
} from "../../../shared/data.js";
|
} from "../../../shared/data.js";
|
||||||
@@ -24,7 +25,7 @@ import { renderMonthlyBudget } from "./budget.js";
|
|||||||
import { effectNodes, money, quotedMoney } from "./format.js";
|
import { effectNodes, money, quotedMoney } from "./format.js";
|
||||||
import { bindTabs } from "./tabs.js";
|
import { bindTabs } from "./tabs.js";
|
||||||
import { ethnicityChart } from "../ui/ethnicity.js";
|
import { ethnicityChart } from "../ui/ethnicity.js";
|
||||||
import { researchNode } from "../ui/card.js";
|
import { cultureNode, researchNode } from "../ui/card.js";
|
||||||
import { TechnologyTreeView } from "../ui/tech_tree_view.js";
|
import { TechnologyTreeView } from "../ui/tech_tree_view.js";
|
||||||
import { resourceNode, valueNode } from "../ui/value.js";
|
import { resourceNode, valueNode } from "../ui/value.js";
|
||||||
import { lineChart } from "../ui/line_chart.js";
|
import { lineChart } from "../ui/line_chart.js";
|
||||||
@@ -2097,6 +2098,7 @@ export class NationModal {
|
|||||||
this._spyRows = new Map();
|
this._spyRows = new Map();
|
||||||
this._reportsSignature = null;
|
this._reportsSignature = null;
|
||||||
this.$intelligenceSpies = $("<div class='politics-card'></div>");
|
this.$intelligenceSpies = $("<div class='politics-card'></div>");
|
||||||
|
this.$intelligenceCulture = $("<div class='politics-card'></div>");
|
||||||
this.$intelligenceCampaigns = $("<div class='politics-card'></div>");
|
this.$intelligenceCampaigns = $("<div class='politics-card'></div>");
|
||||||
this.$intelligenceSpyView.append(
|
this.$intelligenceSpyView.append(
|
||||||
this._buildSpiesCard(),
|
this._buildSpiesCard(),
|
||||||
@@ -2104,6 +2106,7 @@ export class NationModal {
|
|||||||
this._buildSatellitesCard()
|
this._buildSatellitesCard()
|
||||||
);
|
);
|
||||||
this.$intelligencePropagandaView.append(
|
this.$intelligencePropagandaView.append(
|
||||||
|
this._buildCultureCard(),
|
||||||
this.$intelligenceCampaigns,
|
this.$intelligenceCampaigns,
|
||||||
this._buildCampaignForm(civs)
|
this._buildCampaignForm(civs)
|
||||||
);
|
);
|
||||||
@@ -2114,6 +2117,7 @@ export class NationModal {
|
|||||||
this.$intelligenceSub.text(
|
this.$intelligenceSub.text(
|
||||||
"Your spies and the propaganda campaigns they serve."
|
"Your spies and the propaganda campaigns they serve."
|
||||||
);
|
);
|
||||||
|
this._fillCulture(info, localCiv);
|
||||||
this._fillSpies(info, localCiv);
|
this._fillSpies(info, localCiv);
|
||||||
this._fillReports(info);
|
this._fillReports(info);
|
||||||
this._fillSatellites(info);
|
this._fillSatellites(info);
|
||||||
@@ -2122,6 +2126,42 @@ export class NationModal {
|
|||||||
this._syncCampaignForm();
|
this._syncCampaignForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The culture readout: the simple economy funds propaganda from the culture
|
||||||
|
// stock, so the tab names the store and what the running campaigns drain from
|
||||||
|
// it each day. The hard economy pays campaigns from the treasury instead, so
|
||||||
|
// only the stock (soft power) is worth naming there.
|
||||||
|
_buildCultureCard() {
|
||||||
|
const $card = this.$intelligenceCulture;
|
||||||
|
$card.empty();
|
||||||
|
$card.append($("<div class='name'></div>").text("Culture"));
|
||||||
|
const $desc = $("<div class='desc'></div>").text(
|
||||||
|
this._campaignPaidInCulture()
|
||||||
|
? "Propaganda is paid for in culture, drained from this store each day; a " +
|
||||||
|
"campaign lapses when there is too little left to fund it."
|
||||||
|
: "Your stock of soft power. Campaigns are funded from the treasury."
|
||||||
|
);
|
||||||
|
this.$cultureReadout = $("<div class='politics-readout culture-readout'></div>");
|
||||||
|
$card.append($desc, this.$cultureReadout);
|
||||||
|
return $card;
|
||||||
|
}
|
||||||
|
|
||||||
|
_fillCulture(info, localCiv) {
|
||||||
|
if (!this.$cultureReadout || !this.$cultureReadout.length) return;
|
||||||
|
const culture = (info.culture && info.culture[localCiv]) || 0;
|
||||||
|
const upkeep = (info.campaigns || [])
|
||||||
|
.filter((campaign) => campaign.civ === localCiv)
|
||||||
|
.reduce((sum, campaign) => sum + (campaign.hourlyCost || 0), 0);
|
||||||
|
const $readout = this.$cultureReadout.empty().append(cultureNode(culture));
|
||||||
|
if (this._campaignPaidInCulture()) {
|
||||||
|
$readout
|
||||||
|
.append(document.createTextNode(" culture · Campaigns consume "))
|
||||||
|
.append(cultureNode(upkeep * HOURS_PER_DAY))
|
||||||
|
.append(document.createTextNode("/day"));
|
||||||
|
} else {
|
||||||
|
$readout.append(document.createTextNode(" culture"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The spy roster: one row per spy in the field, with a Locate button that
|
// The spy roster: one row per spy in the field, with a Locate button that
|
||||||
// selects it and brings the map to it. Rows are only rebuilt when the roster
|
// selects it and brings the map to it. Rows are only rebuilt when the roster
|
||||||
// (or a spy's whereabouts) changes, so an arriving snapshot does not yank a
|
// (or a spy's whereabouts) changes, so an arriving snapshot does not yank a
|
||||||
@@ -2336,19 +2376,51 @@ export class NationModal {
|
|||||||
}
|
}
|
||||||
for (const campaign of campaigns) {
|
for (const campaign of campaigns) {
|
||||||
const row = this._campaignRows.get(campaign.id);
|
const row = this._campaignRows.get(campaign.id);
|
||||||
if (row) row.$cost.text(this._campaignCostText(info, campaign));
|
if (row) row.$cost.empty().append(this._campaignCostNode(info, campaign));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_campaignCostText(info, campaign) {
|
// Which currency propaganda is funded from: the simple economy drains the
|
||||||
|
// culture stock, the hard economy the treasury. The two models also price a
|
||||||
|
// campaign with their own figures, so the slider bounds come from here too.
|
||||||
|
_campaignPaidInCulture() {
|
||||||
|
return this.economyModel === "simple";
|
||||||
|
}
|
||||||
|
|
||||||
|
_campaignDefaultCost() {
|
||||||
|
return this._campaignPaidInCulture()
|
||||||
|
? SIMPLE_ECONOMY.campaign.defaultCulturePerHour
|
||||||
|
: PROPAGANDA.defaultHourlyCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
_campaignMaxCost() {
|
||||||
|
return this._campaignPaidInCulture()
|
||||||
|
? SIMPLE_ECONOMY.campaign.maxCulturePerHour
|
||||||
|
: PROPAGANDA.maxHourlyCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A campaign figure in its own currency: culture points with the clapperboard
|
||||||
|
// icon in the simple economy, quoted euros in the hard one. `amount` is
|
||||||
|
// already scaled to the period being shown.
|
||||||
|
_campaignAmountNode(amount) {
|
||||||
|
if (this._campaignPaidInCulture()) return cultureNode(amount);
|
||||||
|
return document.createTextNode(
|
||||||
|
`${currencySymbol()}${groupDigits(Math.round(this._quoted(amount)))}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_campaignCostNode(info, campaign) {
|
||||||
const hourly = campaign.hourlyCost || 0;
|
const hourly = campaign.hourlyCost || 0;
|
||||||
const activeHours = Math.max(0, (info.totalHours || 0) - (campaign.startedHours || 0));
|
const activeHours = Math.max(0, (info.totalHours || 0) - (campaign.startedHours || 0));
|
||||||
const spent = hourly * activeHours;
|
const spent = hourly * activeHours;
|
||||||
const days = Math.floor(activeHours / HOURS_PER_DAY);
|
const days = Math.floor(activeHours / HOURS_PER_DAY);
|
||||||
return (
|
const $cost = $("<span class='politics-campaign-cost'></span>");
|
||||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(hourly) * HOURS_PER_DAY))}/day · ${currencySymbol()}${groupDigits(Math.round(spent))} spent over ` +
|
$cost
|
||||||
`${days} day${days === 1 ? "" : "s"}`
|
.append(this._campaignAmountNode(hourly * HOURS_PER_DAY))
|
||||||
);
|
.append(document.createTextNode("/day · "))
|
||||||
|
.append(this._campaignAmountNode(spent))
|
||||||
|
.append(document.createTextNode(` spent over ${days} day${days === 1 ? "" : "s"}`));
|
||||||
|
return $cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildCampaignRow(info, localCiv, campaign) {
|
_buildCampaignRow(info, localCiv, campaign) {
|
||||||
@@ -2369,30 +2441,32 @@ export class NationModal {
|
|||||||
if (campaign.civ === localCiv) {
|
if (campaign.civ === localCiv) {
|
||||||
const $slider = $("<input type='range' class='politics-slider'>").attr({
|
const $slider = $("<input type='range' class='politics-slider'>").attr({
|
||||||
min: 0,
|
min: 0,
|
||||||
max: PROPAGANDA.maxHourlyCost,
|
max: this._campaignMaxCost(),
|
||||||
step: 1000,
|
step: this._campaignPaidInCulture() ? 1 : 1000,
|
||||||
value: campaign.hourlyCost,
|
value: campaign.hourlyCost,
|
||||||
});
|
});
|
||||||
const $spend = $("<span class='politics-readout'></span>").text(
|
const spendNode = (hourly) =>
|
||||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
|
$("<span class='politics-readout'></span>")
|
||||||
);
|
.append(this._campaignAmountNode(hourly * HOURS_PER_DAY))
|
||||||
$slider.on("input", () =>
|
.append(document.createTextNode("/day"));
|
||||||
$spend.text(
|
let $spend = spendNode(campaign.hourlyCost);
|
||||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(Number($slider.val())) * HOURS_PER_DAY))}/day`
|
$slider.on("input", () => {
|
||||||
)
|
const next = spendNode(Number($slider.val()));
|
||||||
);
|
$spend.replaceWith(next);
|
||||||
|
$spend = next;
|
||||||
|
});
|
||||||
$slider.on("change", () => {
|
$slider.on("change", () => {
|
||||||
const amount = Number($slider.val());
|
const amount = Number($slider.val());
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
// Reverting to the last known spend keeps the control honest until
|
// Reverting to the last known spend keeps the control honest until
|
||||||
// the player confirms the stop.
|
// the player confirms the stop.
|
||||||
$slider.val(campaign.hourlyCost);
|
$slider.val(campaign.hourlyCost);
|
||||||
$spend.text(
|
const next = spendNode(campaign.hourlyCost);
|
||||||
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
|
$spend.replaceWith(next);
|
||||||
);
|
$spend = next;
|
||||||
this.onConfirm(
|
this.onConfirm(
|
||||||
"Stop this campaign?",
|
"Stop this campaign?",
|
||||||
`Cutting the spending to ${currencySymbol()}0 will stop the campaign.`,
|
`Cutting the spending to ${this._campaignPaidInCulture() ? "0 culture" : `${currencySymbol()}0`} will stop the campaign.`,
|
||||||
() => this.onCancelCampaign(campaign.id)
|
() => this.onCancelCampaign(campaign.id)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -2422,17 +2496,21 @@ export class NationModal {
|
|||||||
$card.append($("<div class='name'></div>").text("Launch a propaganda campaign"));
|
$card.append($("<div class='name'></div>").text("Launch a propaganda campaign"));
|
||||||
$card.append(
|
$card.append(
|
||||||
$("<div class='desc'></div>").text(
|
$("<div class='desc'></div>").text(
|
||||||
"Spend money to warm or cool a population's view of another people or government. " +
|
`Spend ${this._campaignPaidInCulture() ? "culture" : "money"} to warm or cool a ` +
|
||||||
"Your cultural impact decides how strongly the message lands, and the last " +
|
"population's view of another people or government. Your cultural impact " +
|
||||||
"few popularity points are the hardest to win."
|
"decides how strongly the message lands, and the last few popularity points " +
|
||||||
|
"are the hardest to win."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$card.append(
|
const $beginCost = $("<div class='desc politics-cost'></div>").append(
|
||||||
$("<div class='desc politics-cost'></div>").text(
|
document.createTextNode("Costs "),
|
||||||
`Costs ${currencySymbol()}${groupDigits(Math.round(this._quoted(PROPAGANDA.defaultHourlyCost) * HOURS_PER_DAY))}/day to begin; ` +
|
this._campaignAmountNode(this._campaignDefaultCost() * HOURS_PER_DAY),
|
||||||
"adjust the spending on an active campaign to make it land harder or softer."
|
document.createTextNode(
|
||||||
|
"/day to begin; adjust the spending on an active campaign to make it land " +
|
||||||
|
"harder or softer."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
$card.append($beginCost);
|
||||||
const $observer = this._civSelect(civs, campaign.observer);
|
const $observer = this._civSelect(civs, campaign.observer);
|
||||||
const $kind = $("<select class='politics-select'></select>").append(
|
const $kind = $("<select class='politics-select'></select>").append(
|
||||||
$("<option value='government'></option>").text("their government"),
|
$("<option value='government'></option>").text("their government"),
|
||||||
|
|||||||
@@ -47,6 +47,22 @@ export function researchNode(amount, { sign = "", decimals = 0 } = {}) {
|
|||||||
return $span;
|
return $span;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A culture-point figure with its clapperboard icon, standing in for the word
|
||||||
|
// "culture" the way the erlenmeyer stands in for research. The simple economy
|
||||||
|
// spends culture on propaganda, so the campaign panels read with the same
|
||||||
|
// grammar as the research figures.
|
||||||
|
export function cultureNode(amount, { sign = "", decimals = 0 } = {}) {
|
||||||
|
const proto = resourceById("culture");
|
||||||
|
const $span = $("<span class='res culture-points'></span>");
|
||||||
|
if (proto) {
|
||||||
|
$span.append($("<img>").attr("src", `assets/${proto.icon}`).attr("alt", proto.name));
|
||||||
|
}
|
||||||
|
const value = Number(amount) || 0;
|
||||||
|
const text = decimals > 0 ? value.toFixed(decimals) : groupDigits(Math.round(value));
|
||||||
|
$span.append(document.createTextNode(`${sign}${text}`));
|
||||||
|
return $span;
|
||||||
|
}
|
||||||
|
|
||||||
// An inline resource bill for a card: a muted label, then one icon-and-amount
|
// An inline resource bill for a card: a muted label, then one icon-and-amount
|
||||||
// per material, then an optional suffix. Used for a unit's daily upkeep, which
|
// per material, then an optional suffix. Used for a unit's daily upkeep, which
|
||||||
// is paid in materials rather than money.
|
// is paid in materials rather than money.
|
||||||
|
|||||||
@@ -108,8 +108,22 @@ export const RESEARCH = {
|
|||||||
colour: "#a142b6",
|
colour: "#a142b6",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Culture is the nation's store of soft power: produced by its cultural
|
||||||
|
// buildings and spent, in the simple economy, on propaganda campaigns. Like
|
||||||
|
// research it is a special resource -- it has no market, no stockpile tile and
|
||||||
|
// never moves -- so it is kept out of the tradeable catalogue and shown only
|
||||||
|
// with its clapperboard icon.
|
||||||
|
export const CULTURE = {
|
||||||
|
id: "culture",
|
||||||
|
name: "Culture",
|
||||||
|
unit: "",
|
||||||
|
icon: "icon_culture.svg",
|
||||||
|
colour: "#9a7bff",
|
||||||
|
};
|
||||||
|
|
||||||
export function resourceById(id) {
|
export function resourceById(id) {
|
||||||
if (id === RESEARCH.id) return RESEARCH;
|
if (id === RESEARCH.id) return RESEARCH;
|
||||||
|
if (id === CULTURE.id) return CULTURE;
|
||||||
return RESOURCES[RESOURCE_INDEX[id]] || null;
|
return RESOURCES[RESOURCE_INDEX[id]] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { compact, groupDigits } from "../shared/text_format.js";
|
|||||||
import { tileImprovementResourceCost } from "../shared/resources.js";
|
import { tileImprovementResourceCost } from "../shared/resources.js";
|
||||||
import { quotedBuildCost, marketPricesFrom } from "../client/js/modals/format.js";
|
import { quotedBuildCost, marketPricesFrom } from "../client/js/modals/format.js";
|
||||||
import { parseKey } from "../shared/hex.js";
|
import { parseKey } from "../shared/hex.js";
|
||||||
import { smallState, adjacentLand, grantBuilding, stripPorts } from "./framework/helpers.js";
|
import { smallState, simpleState, adjacentLand, grantBuilding, stripPorts } from "./framework/helpers.js";
|
||||||
|
|
||||||
function stubNetwork() {
|
function stubNetwork() {
|
||||||
return {
|
return {
|
||||||
@@ -1598,6 +1598,37 @@ export class GameScreenTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async test_propaganda_tab_shows_the_culture_drain() {
|
||||||
|
const env = await setupDom();
|
||||||
|
try {
|
||||||
|
const state = simpleState();
|
||||||
|
state.culture.set(0, 12_345);
|
||||||
|
state.requestCampaign(0, 0, "government", 1, 1);
|
||||||
|
const screen = new GameScreen(stubNetwork(), makeConfig());
|
||||||
|
screen.enter();
|
||||||
|
screen.onState(state.snapshot(0));
|
||||||
|
|
||||||
|
env.$("#civ-flag").click();
|
||||||
|
env.$("#modal-nation .tab[data-tab='intelligence']").click();
|
||||||
|
showIntelligenceSubtab(env, "Propaganda");
|
||||||
|
|
||||||
|
const $culture = env.$("#intelligence-propaganda-view .culture-readout");
|
||||||
|
this.assertSize($culture, 1, "the propaganda tab reads out the culture stock");
|
||||||
|
this.assertTrue($culture.text().includes("12\u00a0345"), "it names the current culture");
|
||||||
|
this.assertTrue($culture.text().includes("192"), "and what the campaigns consume");
|
||||||
|
const src = $culture.find("img").first().attr("src");
|
||||||
|
this.assertTrue(/icon_culture\.svg$/.test(src || ""), "with the culture icon");
|
||||||
|
|
||||||
|
const $cost = env.$("#intelligence-propaganda-view .politics-campaign-cost");
|
||||||
|
this.assertSize($cost, 1, "the running campaign reports its cost");
|
||||||
|
this.assertGreater($cost.find("img").length, 0, "in culture points");
|
||||||
|
this.assertFalse($cost.text().includes("\u20ac"), "not in money");
|
||||||
|
screen.leave();
|
||||||
|
} finally {
|
||||||
|
teardownDom(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async test_politics_tab_sends_campaign_and_policy_orders() {
|
async test_politics_tab_sends_campaign_and_policy_orders() {
|
||||||
const env = await setupDom();
|
const env = await setupDom();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user