166 lines
6.2 KiB
JavaScript
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();
|
|
},
|
|
};
|