Altars and bases
Flame Altars are game data, so the platform reads them for every mod. A mod
asks for them during init with
load.altars()
and gets a Copy handle to keep in its state:
use enshwick::events::AltarsChanged;
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> { let altars = load.altars()?; load.on::<AltarsChanged>(move |()| { for altar in altars.list() { enshwick::info!( "altar {} level {} zone {:?}", altar.entity.0, altar.level, altar.zone ); } })}The loader follows the altars in the game server: the dedicated server, or a client while it hosts a game. A client that only joins a server sees none.
Cells and zones
Section titled “Cells and zones”Zones are boxes of whole 0.5 m cells, half-open (min <= p < max) like the
engine’s own point test, with axis 1 up. CellBox has contains, touches
and footprint_area; cell_of_fixed and cell_of_metres turn a position
into its cell.
Each Altar has:
entity,levelandpositionin metres;zone: the zone as the engine has built it now, growth by any mod included;levels: the vanilla zone at every level up to the current one, each inside the next;tilted: an altar tilted off the vertical has no known zone.
list works from any thread and returns an empty list while no world runs.
AltarsChanged is raised on the game thread after a step in which an altar
was placed, removed, raised a level or had its zone built anew. A removed altar
leaves the list a few seconds and some 60 server steps after the game last
showed it, the last altar too, when the list turns empty; a server that stalls
keeps its altars.
The engine groups altars whose zones touch or overlap into one base.
altars.bases() returns those groups, each with its altars, their zones and
the bounds of all of them; Base::contains tells whether a cell lies in one
of its zones. bases_of does the same for a list a mod already holds.
fn based(altars: &enshwick::altars::Altars, cell: [i64; 3]) -> bool { altars.bases().iter().any(|base| base.contains(cell))}Sizing the zones
Section titled “Sizing the zones”Any mod may size the zones:
load.on_altar_zone(|zone| ...)
adds its filter to a chain. When altars change, the loader asks the filters
about every altar, in load order: each sees the zone so far, the vanilla one
first, and may grow it by grow cells on every horizontal side or make it
height cells tall. The loader turns the result into the engine’s own shape
for the altar’s turn and writes it on the server’s next zone pass, while the
altar stays at that level.
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> { // Every altar four cells wider on each side, on top of what mods before // this one gave it. let sizes = load.on_altar_zone(|zone| zone.grow += 4)?; let _ = sizes; Ok(())}Filters run on the server’s game thread, once per altar each time the
altars change. A mod that solves sizes at length does it on a thread of its
own, keeps the answers where its filter reads them, and calls
sizes.changed() from any thread to have the zones sized again on the next
step. A size changes the altar’s built zone and so raises AltarsChanged
again: compare the input you solve from, the levels, not the built zones,
before solving again.
Altars per level and bases past 16
Section titled “Altars per level and bases past 16”load.on_altar_limit(|limit| ...) adds a filter to the chain that decides
how many altars each Flame level allows, on both sides. Each filter, in load
order, sees the level, the game’s own count and the count so far, and sets
allowed; the loader keeps the answer within the zones the game groups. The
game groups only 16 zones into its 16 bases; load.raise_base_limit() lets
it group every zone it lists, allows up to 32 altars a level, and refuses an
altar that would start a 17th base with the game’s own “too many altars”.
With the unstable feature (Stability),
load.request_base_limits(zones, bases) allows up to zones altars a
level, at most 255, every zone the game lists, in up to bases bases, at
most 179; the largest request of every mod holds, and a value past
base_limit().max_zones or max_bases turns the mod off. Both return
false on a game build whose code the platform does not know, where the
game keeps 16. The loader’s hooks go in together or not at all: when one
cannot be armed, the game keeps 16. Bases past 16 need more of the game’s
code than the zones do: where the loader cannot arm all of it, the game
keeps 16 bases with the zones raised, the log says what was missing, and
max_bases reads 16. A server holds the bases its mods asked for. A joined
client with bases past 16 armed groups every base the server does; one
whose bases past 16 could not arm groups 16, and its log says so.
altars::grouping() tells how many zones and bases the last grouping
found, altars::base_limit() the most the game groups now, and BaseLimit::past_vanilla whether a world needs the raised
limit to open.
Building across zones
Section titled “Building across zones”Vanilla accepts a placed object only when both corners of its box lie in one
zone.
load.on_building_zone_check
runs a filter when the game refuses one for that reason, with the zones, the
object’s box and the margin in 32.32 fixed-point metres; returning true
accepts it after all.
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> { load.on_building_zone_check(|check| { check.zones.iter().any(|zone| { (0..3).all(|a| zone.min[a] - check.margin <= check.object.min[a] && check.object.max[a] < zone.max[a] + check.margin) }) })}The check runs on both sides, since a client predicts placement, and while a player aims a piece: keep it cheap and never block in it. A mod that changes placement this way counts in the join check, so every player runs it.