Перейти до вмісту

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.

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, level and position in 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))
}

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.

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.

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.