Reworked building costs
This commit is contained in:
@@ -442,7 +442,6 @@ export const panelMethods = {
|
||||
this.trainModal.close();
|
||||
this.buildingsModal.show(
|
||||
{ id: city.id, name: city.name },
|
||||
(this.snapshot.viewerStats || {}).gdp || 0,
|
||||
(this.snapshot.viewerStats || {}).budget || 0,
|
||||
BUILDINGS,
|
||||
this._cityBuildings.get(city.id) || {}
|
||||
@@ -453,7 +452,6 @@ export const panelMethods = {
|
||||
if (!this.snapshot || !this.buildingsModal.isShowing(this.selectedCityId)) return;
|
||||
const stats = this.snapshot.viewerStats || {};
|
||||
this.buildingsModal.refresh(
|
||||
stats.gdp || 0,
|
||||
stats.budget || 0,
|
||||
this._cityBuildings.get(this.selectedCityId) || {}
|
||||
);
|
||||
|
||||
@@ -27,14 +27,14 @@ export class BuildingsModal {
|
||||
return !this.$modal.hasClass("hidden") && this.cityId === cityId;
|
||||
}
|
||||
|
||||
show(city, gdp, budget, buildings, levels) {
|
||||
show(city, budget, buildings, levels) {
|
||||
this.cityId = city.id;
|
||||
this.cityName = city.name;
|
||||
this.buildings = buildings;
|
||||
this.$title.text(`${city.name} — Buildings`);
|
||||
this.$list.empty();
|
||||
this.rows = buildings.map((proto, index) => this._makeRow(proto, index));
|
||||
this.refresh(gdp, budget, levels);
|
||||
this.refresh(budget, levels);
|
||||
this.$modal.removeClass("hidden");
|
||||
}
|
||||
|
||||
@@ -57,27 +57,21 @@ export class BuildingsModal {
|
||||
return { proto, index, $name, $effects, $cost, $upgrade, $downgrade };
|
||||
}
|
||||
|
||||
refresh(gdp, budget, levels) {
|
||||
refresh(budget, levels) {
|
||||
this.$budget.text(`Budget: €${groupDigits(Math.round(budget))}`);
|
||||
for (const row of this.rows) {
|
||||
const level = levels[row.index] || 0;
|
||||
row.$name.text(`${row.proto.name} — Level ${level} / ${row.proto.maxLevel}`);
|
||||
row.$name.text(`${row.proto.name} — Level ${level}`);
|
||||
row.$effects.text(effectsText(row.proto.effects, level > 0 ? level : 1));
|
||||
const parts = [];
|
||||
if (level > 0) parts.push(`Upkeep €${groupDigits(Math.round(buildingUpkeep(row.proto, gdp, level)))}/h`);
|
||||
if (level < row.proto.maxLevel) {
|
||||
parts.push(`Build cost €${groupDigits(Math.round(buildingBuildCost(row.proto, gdp, level)))}`);
|
||||
}
|
||||
if (level > 0) parts.push(`Upkeep €${groupDigits(Math.round(buildingUpkeep(row.proto, level)))}/h`);
|
||||
parts.push(`Build cost €${groupDigits(Math.round(buildingBuildCost(row.proto, level)))}`);
|
||||
row.$cost.text(parts.join(" · "));
|
||||
if (level < row.proto.maxLevel) {
|
||||
const cost = buildingBuildCost(row.proto, gdp, level);
|
||||
row.$upgrade.text(`Upgrade €${groupDigits(Math.round(cost))}`).prop("disabled", budget < cost);
|
||||
} else {
|
||||
row.$upgrade.text("Max level").prop("disabled", true);
|
||||
}
|
||||
const cost = buildingBuildCost(row.proto, level);
|
||||
row.$upgrade.text(`Upgrade €${groupDigits(Math.round(cost))}`).prop("disabled", budget < cost);
|
||||
if (level > 0) {
|
||||
row.$downgrade
|
||||
.text(`Downgrade +€${groupDigits(Math.round(buildingRefund(row.proto, gdp, level)))}`)
|
||||
.text(`Downgrade +€${groupDigits(Math.round(buildingRefund(row.proto, level)))}`)
|
||||
.prop("disabled", false);
|
||||
} else {
|
||||
row.$downgrade.text("Not built").prop("disabled", true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Buildings that can be raised in a city. Costs and upkeep scale with the
|
||||
// city's share of national GDP, so early levels are cheap and later ones bite.
|
||||
// Buildings that can be raised in a city. Build costs are fixed and grow only
|
||||
// with the level, with no level cap; upkeep is 1% of that cost, so it follows
|
||||
// the level too.
|
||||
|
||||
import { EFFECT_GDP, EFFECT_BUDGET_INCOME, EFFECT_RESEARCH, EFFECT_CULTURE, effect } from "./effects.js";
|
||||
|
||||
@@ -8,13 +9,8 @@ export const BUILDINGS = [
|
||||
id: "market",
|
||||
name: "Market",
|
||||
description: "A hub of local trade. Raises national GDP, with diminishing returns.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.01,
|
||||
baseCost: 5_000_000,
|
||||
costAlpha: 1.6,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.00003,
|
||||
upkeepAlpha: 1.5,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_GDP, 0.04, 0.75)],
|
||||
},
|
||||
@@ -22,13 +18,8 @@ export const BUILDINGS = [
|
||||
id: "bank",
|
||||
name: "Bank",
|
||||
description: "Channels private wealth into the state coffers. Raises budget income.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.02,
|
||||
baseCost: 10_000_000,
|
||||
costAlpha: 1.7,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.00004,
|
||||
upkeepAlpha: 1.6,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_BUDGET_INCOME, 0.05, 0.7)],
|
||||
},
|
||||
@@ -36,13 +27,8 @@ export const BUILDINGS = [
|
||||
id: "research_lab",
|
||||
name: "Research lab",
|
||||
description: "Applies science to industry and government. Produces research points.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.015,
|
||||
baseCost: 100_000_000,
|
||||
costAlpha: 1.65,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.00005,
|
||||
upkeepAlpha: 1.55,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_RESEARCH, 1.0, 0.9)],
|
||||
},
|
||||
@@ -50,13 +36,8 @@ export const BUILDINGS = [
|
||||
id: "university",
|
||||
name: "University",
|
||||
description: "Higher education produces research and a little culture.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.025,
|
||||
baseCost: 100_000_000,
|
||||
costAlpha: 1.7,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.00008,
|
||||
upkeepAlpha: 1.6,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_RESEARCH, 2.0, 0.8), effect(EFFECT_CULTURE, 0.5, 0.7)],
|
||||
},
|
||||
@@ -64,13 +45,8 @@ export const BUILDINGS = [
|
||||
id: "museum",
|
||||
name: "Museum",
|
||||
description: "Preserves national heritage, strengthening culture against foreign ideas.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.02,
|
||||
baseCost: 50_000_000,
|
||||
costAlpha: 1.6,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.00005,
|
||||
upkeepAlpha: 1.55,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_CULTURE, 1.5, 0.85)],
|
||||
},
|
||||
@@ -78,13 +54,8 @@ export const BUILDINGS = [
|
||||
id: "cinema_studio",
|
||||
name: "Cinema studio",
|
||||
description: "Mass media spreads the nation's culture at home and abroad.",
|
||||
maxLevel: 10,
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0.03,
|
||||
baseCost: 50_000_000,
|
||||
costAlpha: 1.75,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0.0001,
|
||||
upkeepAlpha: 1.65,
|
||||
refundFraction: 0.5,
|
||||
effects: [effect(EFFECT_CULTURE, 3.0, 0.7)],
|
||||
},
|
||||
|
||||
@@ -87,8 +87,8 @@ export const economyMethods = {
|
||||
return this._playerAggregates(civ).gdp;
|
||||
},
|
||||
|
||||
getPlayerUpkeep(civ, gdp = -1) {
|
||||
let total = this.getBuildingUpkeep(civ, gdp);
|
||||
getPlayerUpkeep(civ) {
|
||||
let total = this.getBuildingUpkeep(civ);
|
||||
const battle = this._battleUnitIds();
|
||||
for (const unit of this.units) {
|
||||
if (unit.civ !== civ) continue;
|
||||
@@ -100,14 +100,13 @@ export const economyMethods = {
|
||||
return total;
|
||||
},
|
||||
|
||||
getBuildingUpkeep(civ, gdp = -1) {
|
||||
getBuildingUpkeep(civ) {
|
||||
let total = 0;
|
||||
if (gdp < 0) gdp = this.getPlayerGdp(civ);
|
||||
for (const city of this.cities) {
|
||||
if (city.civ !== civ) continue;
|
||||
for (const indexStr of Object.keys(city.buildings)) {
|
||||
const index = Number(indexStr);
|
||||
total += buildingUpkeep(this.protoBuildings[index], gdp, city.buildings[indexStr]);
|
||||
total += buildingUpkeep(this.protoBuildings[index], city.buildings[indexStr]);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
@@ -127,7 +126,7 @@ export const economyMethods = {
|
||||
const incomeSources = this._incomeSources(civ);
|
||||
const modifier = incomeSources.reduce((sum, source) => sum + source.effect, 0);
|
||||
const income = base * (1 + modifier);
|
||||
const buildingSources = this._buildingUpkeepSources(civ, gdp);
|
||||
const buildingSources = this._buildingUpkeepSources(civ);
|
||||
const buildingUpkeep = buildingSources.reduce((sum, source) => sum + source.amount, 0);
|
||||
const unitSources = this._unitUpkeepSources(civ);
|
||||
const unitUpkeep = unitSources.reduce((sum, source) => sum + source.amount, 0);
|
||||
@@ -182,13 +181,13 @@ export const economyMethods = {
|
||||
return this._sortSources(Array.from(sources.values()));
|
||||
},
|
||||
|
||||
_buildingUpkeepSources(civ, gdp) {
|
||||
_buildingUpkeepSources(civ) {
|
||||
const sources = new Map();
|
||||
for (const city of this.cities) {
|
||||
if (city.civ !== civ) continue;
|
||||
for (const indexStr of Object.keys(city.buildings)) {
|
||||
const index = Number(indexStr);
|
||||
const amount = buildingUpkeep(this.protoBuildings[index], gdp, city.buildings[indexStr]);
|
||||
const amount = buildingUpkeep(this.protoBuildings[index], city.buildings[indexStr]);
|
||||
if (amount === 0) continue;
|
||||
const entry = sources.get(index) || { label: this.protoBuildings[index].name, kind: "building", amount: 0 };
|
||||
entry.amount += amount;
|
||||
@@ -322,7 +321,7 @@ export const economyMethods = {
|
||||
const gdp = this.getPlayerGdp(civ);
|
||||
const modifiers = this.getCivModifiers(civ);
|
||||
const income = ECONOMY.budgetIncome(gdp, hours) * (1 + (modifiers[EFFECT_BUDGET_INCOME] || 0));
|
||||
const upkeep = this.getPlayerUpkeep(civ, gdp) * hours;
|
||||
const upkeep = this.getPlayerUpkeep(civ) * hours;
|
||||
this.budgets.set(civ, this.getBudget(civ) + income - upkeep);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -56,9 +56,7 @@ export const orderMethods = {
|
||||
const proto = this.protoBuildings[protoIndex];
|
||||
const civ = city.civ;
|
||||
const level = this.getCityBuildingLevel(city, protoIndex);
|
||||
if (level >= proto.maxLevel) return false;
|
||||
const gdp = this.getPlayerGdp(civ);
|
||||
const cost = buildingBuildCost(proto, gdp, level);
|
||||
const cost = buildingBuildCost(proto, level);
|
||||
if (cost > 0 && this.getBudget(civ) < cost) return false;
|
||||
if (cost > 0) this.budgets.set(civ, this.getBudget(civ) - cost);
|
||||
city.buildings[protoIndex] = level + 1;
|
||||
@@ -73,7 +71,7 @@ export const orderMethods = {
|
||||
const civ = city.civ;
|
||||
const level = this.getCityBuildingLevel(city, protoIndex);
|
||||
if (level <= 0) return false;
|
||||
const refund = buildingRefund(proto, this.getPlayerGdp(civ), level);
|
||||
const refund = buildingRefund(proto, level);
|
||||
if (refund > 0) this.budgets.set(civ, this.getBudget(civ) + refund);
|
||||
if (level <= 1) delete city.buildings[protoIndex];
|
||||
else city.buildings[protoIndex] = level - 1;
|
||||
|
||||
@@ -62,7 +62,7 @@ export const serializationMethods = {
|
||||
return {
|
||||
...headline,
|
||||
budget: this.getBudget(viewerCiv),
|
||||
upkeep: this.getPlayerUpkeep(viewerCiv, headline.gdp),
|
||||
upkeep: this.getPlayerUpkeep(viewerCiv),
|
||||
research: this.getResearch(viewerCiv),
|
||||
culture: this.getCulture(viewerCiv),
|
||||
government: this.getGovernment(viewerCiv),
|
||||
|
||||
+12
-8
@@ -3,19 +3,23 @@
|
||||
|
||||
import { effectValue, CITY_DEFENSE_BONUS } from "./data.js";
|
||||
|
||||
export function buildingBuildCost(proto, gdp, level) {
|
||||
return (proto.baseCost + proto.costGdpFraction * Math.max(gdp, 0)) *
|
||||
Math.pow(proto.costAlpha, Math.max(level, 0));
|
||||
// Fixed cost of raising a building to `level` (0-indexed: the cost of the next
|
||||
// level). It grows only with the level, never with the nation's GDP.
|
||||
export function buildingBuildCost(proto, level) {
|
||||
return proto.baseCost * Math.pow(proto.costAlpha, Math.max(level, 0));
|
||||
}
|
||||
|
||||
export function buildingRefund(proto, gdp, level) {
|
||||
return buildingBuildCost(proto, gdp, level - 1) * proto.refundFraction;
|
||||
export function buildingRefund(proto, level) {
|
||||
return buildingBuildCost(proto, level - 1) * proto.refundFraction;
|
||||
}
|
||||
|
||||
export function buildingUpkeep(proto, gdp, level) {
|
||||
// Recurring upkeep of a building kept at `level`, as a fraction of the cost of
|
||||
// that level. Like the build cost it follows the level alone, never the GDP.
|
||||
export const BUILDING_UPKEEP_FRACTION = 0.01;
|
||||
|
||||
export function buildingUpkeep(proto, level) {
|
||||
if (level <= 0) return 0;
|
||||
const base = proto.baseUpkeep + proto.upkeepGdpFraction * Math.max(gdp, 0);
|
||||
return base * Math.pow(proto.upkeepAlpha, level);
|
||||
return buildingBuildCost(proto, level - 1) * BUILDING_UPKEEP_FRACTION;
|
||||
}
|
||||
|
||||
export function buildingEffectValue(proto, stat, level) {
|
||||
|
||||
@@ -93,13 +93,8 @@ export function gdpBuilding(overrides = {}) {
|
||||
return {
|
||||
id: "test_market",
|
||||
name: "Test market",
|
||||
maxLevel: 10,
|
||||
baseCost: 100,
|
||||
costGdpFraction: 0.001,
|
||||
costAlpha: 2.0,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0,
|
||||
upkeepAlpha: 2.0,
|
||||
refundFraction: 0.5,
|
||||
effects: [{ stat: "gdp", baseValue: 0.5, exponent: 1.0 }],
|
||||
...overrides,
|
||||
|
||||
@@ -30,13 +30,14 @@ export class GameStateBuildingsTest extends TestCase {
|
||||
this.assertFalse(state.requestBuild(cityOf(state, 0).id, index));
|
||||
}
|
||||
|
||||
test_request_build_respects_max_level() {
|
||||
test_request_build_has_no_level_cap() {
|
||||
const state = smallState();
|
||||
state.budgets.set(0, BIG_BUDGET);
|
||||
const index = this.addBuilding(state, gdpBuilding({ maxLevel: 1 }));
|
||||
const cityId = cityOf(state, 0).id;
|
||||
this.assertTrue(state.requestBuild(cityId, index));
|
||||
this.assertFalse(state.requestBuild(cityId, index), "already at max level");
|
||||
const index = this.addBuilding(state, gdpBuilding());
|
||||
const city = cityOf(state, 0);
|
||||
for (let i = 0; i < 20; i++) this.assertTrue(state.requestBuild(city.id, index));
|
||||
this.assertEqual(state.getCityBuildingLevel(city, index), 20);
|
||||
this.assertTrue(state.requestBuild(city.id, index), "still upgradable past the old cap");
|
||||
}
|
||||
|
||||
test_request_build_rejects_unknown_targets() {
|
||||
@@ -67,7 +68,7 @@ export class GameStateBuildingsTest extends TestCase {
|
||||
state.budgets.set(0, BIG_BUDGET);
|
||||
const index = this.addBuilding(
|
||||
state,
|
||||
gdpBuilding({ baseCost: 0, costGdpFraction: 0, baseUpkeep: 100, upkeepAlpha: 1.0 })
|
||||
gdpBuilding({ baseCost: 10_000, costAlpha: 1.0 })
|
||||
);
|
||||
const before = state.getPlayerUpkeep(0);
|
||||
this.assertTrue(state.requestBuild(cityOf(state, 0).id, index));
|
||||
@@ -77,7 +78,7 @@ export class GameStateBuildingsTest extends TestCase {
|
||||
test_research_buildings_accumulate_points_each_hour() {
|
||||
const state = smallState();
|
||||
state.budgets.set(0, BIG_BUDGET);
|
||||
const building = gdpBuilding({ baseCost: 0, costGdpFraction: 0, effects: [] });
|
||||
const building = gdpBuilding({ baseCost: 0, effects: [] });
|
||||
building.effects.push({ stat: EFFECT_RESEARCH, baseValue: 2.0, exponent: 1.0 });
|
||||
const index = this.addBuilding(state, building);
|
||||
this.assertTrue(state.requestBuild(cityOf(state, 0).id, index));
|
||||
@@ -95,7 +96,7 @@ export class GameStateBuildingsTest extends TestCase {
|
||||
state.budgets.set(0, BIG_BUDGET);
|
||||
const index = this.addBuilding(
|
||||
state,
|
||||
gdpBuilding({ baseCost: 0, costGdpFraction: 0, baseUpkeep: 100, upkeepAlpha: 1.0, effects: [] })
|
||||
gdpBuilding({ baseCost: 10_000, costAlpha: 1.0, effects: [] })
|
||||
);
|
||||
this.assertTrue(state.requestBuild(cityOf(state, 0).id, index));
|
||||
const breakdown = state.viewerStats(0).breakdown;
|
||||
@@ -117,7 +118,7 @@ export class GameStateBuildingsTest extends TestCase {
|
||||
const effect = { stat: EFFECT_BUDGET_INCOME, baseValue: 0.05, exponent: 1.0 };
|
||||
const index = this.addBuilding(
|
||||
state,
|
||||
gdpBuilding({ baseCost: 0, costGdpFraction: 0, baseUpkeep: 0, effects: [effect] })
|
||||
gdpBuilding({ baseCost: 0, effects: [effect] })
|
||||
);
|
||||
this.assertTrue(state.requestBuild(cityOf(state, 0).id, index));
|
||||
const breakdown = state.viewerStats(0).breakdown;
|
||||
|
||||
@@ -86,7 +86,7 @@ export class BuildingsModalTest extends TestCase {
|
||||
modal.onDemolish = (cityId, index) => {
|
||||
demolished = [cityId, index];
|
||||
};
|
||||
modal.show({ id: 3, name: "Lyon" }, 1.0e9, 1.0e15, BUILDINGS, {});
|
||||
modal.show({ id: 3, name: "Lyon" }, 1.0e15, BUILDINGS, {});
|
||||
this.assertEqual(env.$("#buildings-title").text(), "Lyon — Buildings");
|
||||
this.assertSize(env.$("#buildings-list .row-card"), BUILDINGS.length);
|
||||
const first = env.$("#buildings-list .row-card").first();
|
||||
@@ -95,7 +95,7 @@ export class BuildingsModalTest extends TestCase {
|
||||
first.find(".actions button").first().click();
|
||||
this.assertEqual(built, [3, 0]);
|
||||
|
||||
modal.refresh(1.0e9, 1.0e15, { 0: 1 });
|
||||
modal.refresh(1.0e15, { 0: 1 });
|
||||
this.assertTrue(first.find(".name").text().includes("Level 1"));
|
||||
first.find(".actions button").eq(1).click();
|
||||
this.assertEqual(demolished, [3, 0]);
|
||||
@@ -110,7 +110,7 @@ export class BuildingsModalTest extends TestCase {
|
||||
const env = await setupDom();
|
||||
try {
|
||||
const modal = new BuildingsModal();
|
||||
modal.show({ id: 1, name: "X" }, 1.0e9, 0, BUILDINGS, {});
|
||||
modal.show({ id: 1, name: "X" }, 0, BUILDINGS, {});
|
||||
const upgrade = env.$("#buildings-list .row-card").first().find(".actions button").first();
|
||||
this.assertTrue(upgrade.prop("disabled"));
|
||||
} finally {
|
||||
|
||||
@@ -94,14 +94,13 @@ export class EconomyOrderEdgesTest extends TestCase {
|
||||
this.assertNull(state.getTraining(city.id), "nothing is left in the queue");
|
||||
}
|
||||
|
||||
test_building_stops_at_the_maximum_level() {
|
||||
test_buildings_have_no_level_cap() {
|
||||
const state = smallState();
|
||||
const city = cityOf(state, 0);
|
||||
const proto = state.protoBuildings[0];
|
||||
state.budgets.set(0, 1e15);
|
||||
for (let i = 0; i < proto.maxLevel; i++) state.requestBuild(city.id, 0);
|
||||
this.assertEqual(state.getCityBuildingLevel(city, 0), proto.maxLevel);
|
||||
this.assertFalse(state.requestBuild(city.id, 0), "cannot build past the cap");
|
||||
for (let i = 0; i < 12; i++) state.requestBuild(city.id, 0);
|
||||
this.assertEqual(state.getCityBuildingLevel(city, 0), 12);
|
||||
this.assertTrue(state.requestBuild(city.id, 0), "levels keep rising without a cap");
|
||||
}
|
||||
|
||||
test_demolishing_removes_one_level_and_refunds() {
|
||||
@@ -147,9 +146,8 @@ export class EconomyOrderEdgesTest extends TestCase {
|
||||
|
||||
test_buildings_in_the_catalogue_have_a_cost_curve() {
|
||||
for (const proto of BUILDINGS) {
|
||||
this.assertTrue(proto.costGdpFraction > 0, `${proto.id} cost fraction`);
|
||||
this.assertTrue(proto.baseCost > 0, `${proto.id} base cost`);
|
||||
this.assertTrue(proto.costAlpha > 1, `${proto.id} cost growth`);
|
||||
this.assertTrue(proto.maxLevel >= 1, `${proto.id} max level`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-19
@@ -25,11 +25,7 @@ import {
|
||||
function proto(overrides = {}) {
|
||||
return {
|
||||
baseCost: 0,
|
||||
costGdpFraction: 0,
|
||||
costAlpha: 1.5,
|
||||
baseUpkeep: 0,
|
||||
upkeepGdpFraction: 0,
|
||||
upkeepAlpha: 1.5,
|
||||
refundFraction: 0.5,
|
||||
effects: [],
|
||||
...overrides,
|
||||
@@ -38,29 +34,30 @@ function proto(overrides = {}) {
|
||||
|
||||
export class RulesTest extends TestCase {
|
||||
test_build_cost_grows_exponentially_with_level() {
|
||||
const building = proto({ baseCost: 100, costGdpFraction: 0.01, costAlpha: 2.0 });
|
||||
this.assertApprox(buildingBuildCost(building, 1000, 0), 110);
|
||||
this.assertApprox(buildingBuildCost(building, 1000, 1), 220);
|
||||
this.assertApprox(buildingBuildCost(building, 1000, 2), 440);
|
||||
const building = proto({ baseCost: 100, costAlpha: 2.0 });
|
||||
this.assertApprox(buildingBuildCost(building, 0), 100);
|
||||
this.assertApprox(buildingBuildCost(building, 1), 200);
|
||||
this.assertApprox(buildingBuildCost(building, 2), 400);
|
||||
}
|
||||
|
||||
test_build_cost_is_based_on_gdp() {
|
||||
const building = proto({ costGdpFraction: 0.01 });
|
||||
this.assertApprox(buildingBuildCost(building, 5000, 0), 50);
|
||||
this.assertApprox(buildingBuildCost(building, 5000, 0) * 2, buildingBuildCost(building, 10000, 0));
|
||||
test_build_cost_is_fixed_and_independent_of_gdp() {
|
||||
const building = proto({ baseCost: 100, costAlpha: 1.5 });
|
||||
this.assertApprox(buildingBuildCost(building, 0), 100);
|
||||
this.assertApprox(buildingBuildCost(building, 3), 100 * Math.pow(1.5, 3));
|
||||
}
|
||||
|
||||
test_upkeep_grows_exponentially_with_level() {
|
||||
const building = proto({ baseUpkeep: 10, upkeepAlpha: 1.5 });
|
||||
this.assertEqual(buildingUpkeep(building, 1000, 0), 0);
|
||||
this.assertApprox(buildingUpkeep(building, 1000, 1), 15);
|
||||
this.assertApprox(buildingUpkeep(building, 1000, 2), 22.5);
|
||||
test_upkeep_is_one_percent_of_the_levels_build_cost() {
|
||||
const building = proto({ baseCost: 100, costAlpha: 2.0 });
|
||||
this.assertEqual(buildingUpkeep(building, 0), 0);
|
||||
this.assertApprox(buildingUpkeep(building, 1), 1);
|
||||
this.assertApprox(buildingUpkeep(building, 2), 2);
|
||||
this.assertApprox(buildingUpkeep(building, 3), 4);
|
||||
}
|
||||
|
||||
test_refund_is_a_fraction_of_the_removed_level() {
|
||||
const building = proto({ baseCost: 100, refundFraction: 0.5 });
|
||||
this.assertApprox(buildingRefund(building, 0, 1), 50);
|
||||
this.assertApprox(buildingRefund(building, 0, 2), 75);
|
||||
this.assertApprox(buildingRefund(building, 1), 50);
|
||||
this.assertApprox(buildingRefund(building, 2), 75);
|
||||
}
|
||||
|
||||
test_effect_is_linear_or_sublinear() {
|
||||
|
||||
Reference in New Issue
Block a user