Adding a Tier
Step-by-step guide for adding a new housing tier or modifying an existing one.
Modify vs. Add
Most balance changes — thresholds, risk values, population — require only editing houses.js. No recompile is needed if the engine is run in hot-reload mode (--dev flag). The C++ layer only needs touching when you add a genuinely new tier that didn't exist before.
| Change type | Files to edit | Recompile needed? |
|---|---|---|
| Adjust a threshold, risk, or population value | houses.js only | No (hot-reload) |
| Change goods or service requirements | houses.js only | No |
| Change animations / sprites | houses.js only | No |
| Add a new tier between two existing tiers | houses.js + building_type.h + new C++ class | Yes |
| Rename a tier (display name) | houses.js + localisation strings | No |
Step 1 — Add a config block in houses.js
Insert a new building_config.add({...}) call at the correct position in the file (tier order matters for evolution chain lookup). Copy the nearest existing tier as a template and adjust all values. See the Config Format page for a full field reference.
// src/scripts/houses.js
building_config.add({
type: "BUILDING_HOUSE_MY_NEW_TIER", // must match enum name exactly
animations: {
base: { pack: 18, id: 5, offset: 42 },
preview: { pack: 18, id: 5, offset: 42 },
house: { pack: 18, id: 5, offset: 42 },
minimap: { pack: 18, id: 5, offset: 0 }
},
building_size: 1,
can_merge: true,
desirability: { value: 0, step: 1, step_size: 1, range: 2 },
crime: { base: 5, value: 0 },
model: {
devolve_desirability: [-5, 0, 5, 10, 15],
evolve_desirability: [ 5, 10, 15, 20, 25],
max_people: [17, 16, 14, 12, 9],
// ... all other required fields
}
});
Step 2 — Add the enum value in building_type.h
Find the housing block in the enum and insert the new value in the correct tier position. The enum values for housing run sequentially — the numeric order determines the evolution chain, so placement matters.
// src/building/building_type.h
enum e_building_type {
// ...
BUILDING_HOUSE_ORDINARY_COTTAGE,
BUILDING_HOUSE_MY_NEW_TIER, // ← insert here
BUILDING_HOUSE_MODEST_HOMESTEAD,
// ...
};
After this change, recompile. All existing save files will still load — the engine handles missing building types gracefully — but houses that were previously at tiers above your insertion point may map to the shifted enum value. Test with a fresh scenario after adding a new tier.
Step 3 — Create the C++ class
Add a struct in building_house.h. Housing tier classes are intentionally minimal — everything interesting lives in the base class or is data-driven from config.
// src/building/building_house.h
struct building_house_my_new_tier : building_house {
BUILDING_METAINFO(BUILDING_HOUSE_MY_NEW_TIER,
building_house_my_new_tier,
building_house)
virtual void evolve(house_demands* demands) override;
};
Step 4 — Register with METAINFO
The BUILDING_METAINFO macro (defined in building_impl.h) does three things:
- Registers the class in the building factory so it can be instantiated by type ID.
- Enables type-safe downcasting via
dcast<building_house_my_new_tier>(). - Wires up static param lookup so
building_house_my_new_tier::params()returns the config loaded fromhouses.js.
No additional registration calls are needed — the macro handles everything through static initialisation.
Step 5 — Implement evolve()
Add the implementation in building_house.cpp. The standard pattern is identical for all tiers — just change the target type names for the transitions.
// src/building/building_house.cpp
void building_house_my_new_tier::evolve(house_demands* demands) {
if (base.house_population == 0) return;
// Attempt merge (only relevant if can_merge == true in config)
if (merge()) return;
e_house_progress result = check_requirements(demands);
if (result == HOUSE_DECAY && !has_devolve_delay()) return;
if (result == HOUSE_EVOLVE) {
change_to(BUILDING_HOUSE_MODEST_HOMESTEAD); // next tier up
} else if (result == HOUSE_DECAY) {
change_to(BUILDING_HOUSE_ORDINARY_COTTAGE); // one tier down
}
}
If your new tier crosses a size boundary (e.g. 2×2 → 3×3), use expand_to_manor() instead of change_to() for the evolve path, and devolve_to_fancy_residence() for the decay path.
Step 6 — Update neighbouring tiers
The tiers on either side of your new tier need to point to it as their evolution target or devolution fallback:
// The tier just below yours — update its EVOLVE case:
void building_house_ordinary_cottage::evolve(house_demands* demands) {
// ...
if (result == HOUSE_EVOLVE)
change_to(BUILDING_HOUSE_MY_NEW_TIER); // was BUILDING_HOUSE_MODEST_HOMESTEAD
// ...
}
// The tier just above yours — update its DECAY case:
void building_house_modest_homestead::evolve(house_demands* demands) {
// ...
if (result == HOUSE_DECAY)
change_to(BUILDING_HOUSE_MY_NEW_TIER); // was BUILDING_HOUSE_ORDINARY_COTTAGE
// ...
}
Checklist
| Done? | Task |
|---|---|
| ☐ | houses.js — new building_config.add() block with all model fields filled in |
| ☐ | building_type.h — enum value added in the correct position |
| ☐ | building_house.h — struct with BUILDING_METAINFO and evolve() declaration |
| ☐ | building_house.cpp — evolve() implementation with correct up/down targets |
| ☐ | Lower neighbour tier's evolve() updated to point up to new tier |
| ☐ | Upper neighbour tier's evolve() updated to devolve down to new tier |
| ☐ | Compiles without errors (static_assert will fail if runtime_data_t exceeds 186 bytes) |
| ☐ | Tested in-game: houses evolve up to new tier and devolve correctly when a service is removed |
| ☐ | Player-facing wiki table in docs/wiki/player/buildings/housing.html updated with the new row |