Files
Battle-for-Tismo/shared/game_state/taxes.js
T

166 lines
6.2 KiB
JavaScript

// Trade taxes: the government's only revenue. There is no GDP tax, so the
// state lives on what crosses its borders and what its regions trade among
// themselves:
//
// - a national sales tax on every purchase a region makes from another region
// of the same country, paid by the buyer on top of the price;
// - an export tariff a foreign buyer pays to the seller's government, in the
// seller's currency, on top of what the seller receives;
// - an import tariff a region pays its own government on the national
// currency it spends acquiring a foreign currency for a purchase.
//
// Rates are set per nation, with an optional per-resource override, so a
// government can protect or tax one commodity differently from the rest.
import { RESOURCE_IDS, TAXES } from "../data.js";
import { NEWS_TARIFF } from "../data/relations.js";
function defaultTaxConfig() {
return {
sales: TAXES.defaultSales,
export: TAXES.defaultExport,
import: TAXES.defaultImport,
// Per-resource overrides, keyed resource id -> { sales?, export?, import? }.
overrides: {},
};
}
export const taxMethods = {
_initTaxes() {
this.taxRates = new Map();
// The current day's tax take per nation, in treasury units, reset by the
// resource tick alongside the other daily ledgers.
this.taxLedger = new Map();
// The current day's tax take per region that generated it, so the region
// tables can show what each contributes.
this.cityTaxLedger = new Map();
// The world commodity price index move of the last day, a fraction.
this.commodityInflation = 0;
this._prevResourcePrices = null;
},
// A nation's live tax config, created with the defaults on first read.
getTaxConfig(civ) {
if (civ < 0) return null;
let config = this.taxRates ? this.taxRates.get(civ) : null;
if (!config) {
config = defaultTaxConfig();
if (!this.taxRates) this.taxRates = new Map();
this.taxRates.set(civ, config);
}
return config;
},
// The rate a nation charges for one tax kind, on one resource: the resource
// override when it has one, otherwise the nation's default rate.
getTaxRate(civ, kind, resource = null) {
const config = this.getTaxConfig(civ);
if (!config) return 0;
const override = resource && config.overrides[resource]
? config.overrides[resource][kind]
: undefined;
return override === undefined ? (config[kind] || 0) : override;
},
// Sets a nation's default rate for a kind, or a single resource's override.
// Returns the value applied.
setTaxRate(civ, kind, rate, resource = null) {
if (civ < 0 || !TAXES.kinds.includes(kind)) return null;
const value = Math.max(0, Math.min(TAXES.maxRate, Number(rate) || 0));
const config = this.getTaxConfig(civ);
if (resource) {
const entry = config.overrides[resource] || (config.overrides[resource] = {});
entry[kind] = value;
} else {
config[kind] = value;
}
return value;
},
// A player changes a tax. Changing an import or export tariff notifies the
// other nations; the sales tax is purely domestic and passes quietly.
requestSetTaxRate(civ, kind, rate, resource = null) {
if (civ < 0 || civ >= this.civilisations.length) return false;
const value = Number(rate);
if (!Number.isFinite(value) || value < 0) return false;
if (!TAXES.kinds.includes(kind)) return false;
if (resource !== null && !RESOURCE_IDS.includes(resource)) return false;
const before = this.getTaxRate(civ, kind, resource);
const applied = this.setTaxRate(civ, kind, value, resource);
if (applied === null) return false;
// A no-op apply (the box was left unchanged) is not news.
if (applied !== before && (kind === "import" || kind === "export")) {
this._addNews({ type: NEWS_TARIFF, civ, kind, rate: applied, resource: resource || null });
}
this._emitChanged();
return true;
},
// The viewer's tax config, as plain data: each kind's default rate and the
// resource overrides that differ from it.
getTaxConfigView(civ) {
const config = this.getTaxConfig(civ);
const result = {};
for (const kind of TAXES.kinds) {
const resources = {};
for (const id of RESOURCE_IDS) {
const override = config.overrides[id] ? config.overrides[id][kind] : undefined;
if (override !== undefined) resources[id] = override;
}
result[kind] = { rate: config[kind] || 0, resources };
}
return result;
},
// Books a tax payment into the treasury and the day's ledgers. `city` is the
// region that generated it (the buyer, or the seller for an export tariff).
_recordTax(civ, kind, resource, amount, city = null) {
if (civ < 0 || !(amount > 0)) return;
this.budgets.set(civ, this.getBudget(civ) + amount);
const category = kind === "sales" ? "taxSales" : kind === "export" ? "taxExport" : "taxImport";
this._recordBudgetCash(civ, category, amount);
if (!this.taxLedger) this.taxLedger = new Map();
let day = this.taxLedger.get(civ);
if (!day) {
day = { sales: 0, export: 0, import: 0 };
this.taxLedger.set(civ, day);
}
day[kind] += amount;
if (city) {
if (!this.cityTaxLedger) this.cityTaxLedger = new Map();
let region = this.cityTaxLedger.get(city.id);
if (!region) {
region = { cityId: city.id, sales: 0, export: 0, import: 0 };
this.cityTaxLedger.set(city.id, region);
}
region[kind] += amount;
}
},
// The taxes one nation collected over the last day, in treasury units.
getTaxTake(civ) {
const day = this.taxLedger ? this.taxLedger.get(civ) : null;
return {
sales: day ? day.sales : 0,
export: day ? day.export : 0,
import: day ? day.import : 0,
};
},
// The taxes one region generated over the last day, in treasury units.
getCityTaxTake(city) {
const region = this.cityTaxLedger ? this.cityTaxLedger.get(city.id) : null;
return {
sales: region ? region.sales : 0,
export: region ? region.export : 0,
import: region ? region.import : 0,
};
},
// Clears the daily tax ledgers, called from the resource day tick.
_resetTaxLedger() {
this.taxLedger = new Map();
this.cityTaxLedger = new Map();
},
};