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

The map

Цей контент ще не доступний вашою мовою.

Unstable: everything in this chapter needs the unstable feature and may change in any release (Stability).

load.map() returns the Map of the game server, dedicated or hosted by a client: the markers the world comes with, its frame and the waypoint each player sets. It is Copy; get it during init and move it into handlers and tasks. Every call belongs on the server’s game thread, like the world’s.

use core::ops::ControlFlow;
use std::time::Duration;
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
let map = load.map()?;
let world = load.world()?;
load.scheduler()?.every(Duration::from_secs(30), move || {
let markers = map.markers().map_or(0, |markers| markers.len());
let waypoints = world
.players()
.iter()
.filter(|player| matches!(map.waypoint(player.entity), Ok(Some(_))))
.count();
enshwick::info!("{markers} markers, {waypoints} waypoints set");
ControlFlow::Continue(())
});
Ok(())
}

map.markers() lists every marker the scene places, in the scene’s order: towns, ruins, dungeons, Flame shrines, quest areas and the like. Each MapMarker has its position in metres, its type and the text tag of its label. The loader reads them from the game’s data once per scene, apart from the game thread, so they are there with no player near and later calls are cheap. Until that read is done, a second or two after the first call, they fail with NotLoaded: ask again later. map.markers_in(a, b) keeps those in the box between two corners, edges included. They fail with a WorldError: WrongThread off the game thread, Missing while no world runs or when the scene does not read on the game build, and Refused for a corner that is not finite.

A marker’s type is a MapMarkerTypeId, the key of the game’s marker type registry. The registry is game data and grows with the game, so the API has no fixed list of types. A client reads the registry with Ui::map_marker_types (Map markers), whose MapMarkerType entries carry the same keys with their group and flags; a dedicated server’s data has no registry. A marker without a type or a label has None there.

The label is a text tag. A client reads its text with enshwick::i18n::game_text(label.id()); a dedicated server has no texts.

map.waypoint(player) returns where a player’s waypoint is, the one every player in the game sees, in metres; None while it is off. The server keeps it only while the player plays, never in the save. It fails like markers(), and with Missing on a game build without waypoints.

map.frame() returns a MapFrame: where the world’s map starts, how far it reaches and the size of the game’s map cells, all in metres on the world’s x and z axes. A map drawn outside the game puts its corner at origin and spans size. On the main scene of the current game the frame starts at (0, 0) and spans 10 240 m each way in cells of 8 m.

use enshwick::map::Map;
fn place(map: Map, position: [f64; 3]) -> Option<[f64; 2]> {
let frame = map.frame().ok()?;
let across = |axis: usize, of: usize| (position[axis] - frame.origin[of]) / frame.size[of];
Some([across(0, 0), across(2, 1)])
}

The loader reads it once per scene like the markers, so it fails like markers(): NotLoaded until that read is done, WrongThread off the game thread, and Missing while no world runs, for a scene without a map, or when the scene does not read on the game build.

map.snapshot(altars) takes what the map shows now as a MapSnapshot: every player with position and waypoint, the Flame Altars of an Altars from load.altars() with the base each belongs to, every marker and the map’s frame, None for a scene without one. It is plain serde data in Enshwick’s own format, for a process beside the server such as a web map. to_bytes() writes MAP_SNAPSHOT_FORMAT in two bytes and then the fields in postcard’s encoding; MapSnapshot::from_bytes reads it back and refuses any other format. A snapshot carries a marker’s label as a text id only, since a dedicated server has no texts. Take one every few seconds, not every step: it walks every marker.

load.fog_of_war() asks the loader to copy the client’s fog of war for the mod, during init; on a dedicated server it fails, since only the player’s machine has the fog. The loader copies it as the game uncovers it, so FogOfWar reads from any thread.

fog.state() returns a FogState: the image’s size in pixels, the metres one pixel covers, the world’s z size, a version that grows whenever pixels may have changed, and a session that changes with each world the client enters. Once the client leaves the world, the copy is gone until it enters one again. state.pixel_of(position) finds the pixel of a place in metres: row 0 is the world’s high z edge, and x grows with world x. fog.read(min, size) copies a box of pixels as a FogImage, one byte each from 0 (hidden) to 255 (uncovered), and fog.coverage(position) reads one place. While the client is in no world, on a game build without the fog, or after the loader stopped copying it, they fail with FogError::Missing.

The markers a player places by hand and the map’s image live only on the player’s machine, and so does the fog of war. A server never sees them.