enshwick 1.0.0-alpha.11

The Enshwick Loader's modding API for Enshrouded: the one crate mods are written against
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
//! The world's map as the server knows it: its markers, its frame and
//! each player's waypoint.
//!
//! Unstable: needs the `unstable` feature and may change in any release.
//!
//! A marker is a `MapMarker` the world comes with: a town, a ruin, a
//! dungeon, a Flame shrine, a quest area, as the scene's own data places
//! it. Its type is a key into the game's marker type registry, an open set
//! the game's data defines. What a player has uncovered of the map lives
//! only on that player's machine.
//!
//! # Examples
//!
//! ```
//! 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(10), move || {
//!         for player in world.players() {
//!             if let Ok(Some(at)) = map.waypoint(player.entity) {
//!                 enshwick::info!("{} marks {at:?}", player.name);
//!             }
//!         }
//!         ControlFlow::Continue(())
//!     });
//!     Ok(())
//! }
//! ```

mod fog;
mod snapshot;

pub use fog::{FogError, FogImage, FogOfWar, FogState};

pub use snapshot::{
    MAP_SNAPSHOT_FORMAT, MapAltar, MapPlayer, MapPoint, MapSnapshot, SnapshotError,
};

use crate::Entity;
use crate::Host;
use crate::WorldError;
use crate::abi::{MapFrameInfo, MapMarkerInfo, Status};
use crate::altars::Altars;
use crate::content::{EngineFacts, Guid};
use crate::ecs::ComponentType;
use crate::engine_ids as ids;
use crate::world::World;
use crate::{Load, Off};

/// The key of a map marker type, as the game's marker type registry
/// numbers them (`MapMarkerTypeId`).
///
/// # Examples
///
/// ```
/// use enshwick::content::Guid;
/// use enshwick::map::MapMarkerTypeId;
///
/// let reference = Guid::parse("5d2e6a2b-0c3f-4d8e-9a71-3e5f0b1c2d4e");
/// assert_eq!(MapMarkerTypeId::from_guid(reference).get(), 0xc807_9fa5);
/// assert_eq!(MapMarkerTypeId::new(7).get(), 7);
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MapMarkerTypeId(u32);

impl MapMarkerTypeId {
    /// The type with registry key `id`.
    ///
    /// # Examples
    ///
    /// See [`MapMarkerTypeId`].
    #[must_use]
    pub const fn new(id: u32) -> Self {
        Self(id)
    }

    /// The type a marker's `markerType` reference names, keyed as the
    /// registry keys it: [`Guid::fnv1a`] of the reference.
    ///
    /// # Examples
    ///
    /// See [`MapMarkerTypeId`].
    #[must_use]
    pub const fn from_guid(reference: Guid) -> Self {
        Self(reference.fnv1a())
    }

    /// The registry key.
    ///
    /// # Examples
    ///
    /// See [`MapMarkerTypeId`].
    #[must_use]
    pub const fn get(self) -> u32 {
        self.0
    }
}

/// A marker on the map that the world comes with: a `MapMarker` the
/// scene places.
///
/// # Examples
///
/// ```
/// use enshwick::map::{Map, MapMarker};
///
/// fn labelled(map: Map) -> Vec<MapMarker> {
///     let mut markers = map.markers().unwrap_or_default();
///     markers.retain(|marker| marker.label.is_some());
///     markers
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MapMarker {
    /// Where it is, in metres.
    pub position: [f64; 3],
    /// Its type; `None` for a marker without one.
    pub marker_type: Option<MapMarkerTypeId>,
    /// The text tag of its label, the game's `markerId`, which
    /// [`game_text`](crate::i18n::game_text) reads by [`Guid::fnv1a`] on a
    /// client; `None` for a marker without one.
    pub label: Option<Guid>,
}

/// Where the map of the world starts and how far it reaches, in metres on
/// the world's x and z axes. The name is Enshwick's own: the game keeps
/// these facts with the scene's biome map.
///
/// A map image or grid drawn over the world puts its corner at `origin` and
/// spans `size`; the game's own map divides it into cells of
/// `metres_per_cell` on each side.
///
/// # Examples
///
/// ```
/// use enshwick::map::MapFrame;
///
/// let frame = MapFrame { origin: [0.0, 0.0], size: [10240.0, 10240.0], metres_per_cell: 8.0 };
/// let cells = frame.size.map(|metres| metres / frame.metres_per_cell);
/// assert_eq!(cells, [1280.0, 1280.0]);
/// ```
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MapFrame {
    /// The map's corner of lowest x and z, in metres, x then z.
    pub origin: [f64; 2],
    /// How far the map reaches from `origin`, in metres, x then z.
    pub size: [f64; 2],
    /// The metres one cell of the game's map covers on each axis.
    pub metres_per_cell: f64,
}

impl MapFrame {
    /// The frame `info` describes, refused unless every value is finite and
    /// the size and cell are above zero.
    fn from_info(info: &MapFrameInfo) -> Option<Self> {
        let finite = info
            .origin
            .iter()
            .chain(&info.size)
            .chain([&info.metres_per_cell])
            .all(|value| value.is_finite());
        let positive = info
            .size
            .iter()
            .chain([&info.metres_per_cell])
            .all(|&value| value > 0.0);
        (finite && positive).then_some(Self {
            origin: info.origin,
            size: info.size,
            metres_per_cell: info.metres_per_cell,
        })
    }
}

/// The map's reads on the game server, dedicated or hosted by the client.
///
/// Every call belongs on the server's game thread, as for
/// [`World`]: from a game event or a
/// [`Scheduler`](crate::schedule::Scheduler) task.
///
/// # Examples
///
/// See the [module](self).
#[derive(Clone, Copy, Debug)]
pub struct Map(&'static Resolved);

/// The map's component types and field offsets, resolved once per mod life.
#[derive(Debug)]
struct Resolved {
    host: Host,
    world: World,
    waypoint: Option<Waypoint>,
}

/// Where a `PlayerWaypoint` keeps its fields.
#[derive(Debug)]
struct Waypoint {
    ty: ComponentType,
    enabled_at: usize,
    position_at: usize,
}

static RESOLVED: crate::state::ModLocal<Resolved> = crate::state::ModLocal::new();

/// The offset of field `id` of type `ty`, refused unless `width` bytes
/// from there lie inside the type, so every read of the field stays in
/// the component.
fn field(load: &Load<'_>, ty: &str, id: &str, width: usize) -> Result<usize, Off> {
    let at = load.offset(id)?;
    let size = load.size(ty)?;
    if at.checked_add(width).is_some_and(|end| end <= size) {
        Ok(at)
    } else {
        Err(Off::new(format!(
            "{id} at {at} does not hold {width} bytes in {ty} of {size}"
        )))
    }
}

impl Waypoint {
    fn resolve(load: &Load<'_>) -> Result<Self, Off> {
        Ok(Self {
            ty: ComponentType::resolve(load, ids::PLAYER_WAYPOINT)?,
            enabled_at: field(load, ids::PLAYER_WAYPOINT, ids::PLAYER_WAYPOINT_ENABLED, 1)?,
            position_at: field(
                load,
                ids::PLAYER_WAYPOINT,
                ids::PLAYER_WAYPOINT_POSITION,
                24,
            )?,
        })
    }
}

impl Map {
    pub(crate) fn resolve(load: &mut Load<'_>) -> Result<Self, Off> {
        if let Some(resolved) = RESOLVED.get() {
            return Ok(Self(resolved));
        }
        let world = load.world()?;
        let resolved = Resolved {
            host: load.host(),
            world,
            waypoint: Waypoint::resolve(load)
                .inspect_err(|off| crate::debug!("waypoint reads off: {off}"))
                .ok(),
        };
        Ok(Self(RESOLVED.get_or_init(|| resolved)))
    }

    /// `entity`'s component of type `ty`, `None` when it has none.
    fn live(self, entity: Entity, ty: ComponentType) -> Result<Option<*const u8>, WorldError> {
        let host = self.0.host;
        let mut out: *const core::ffi::c_void = core::ptr::null();
        let status = (host.api().component)(host.handle(), entity, ty.hash(), &raw mut out);
        match status {
            Status::MISSING => Ok(None),
            status => WorldError::from_status(status)
                .map(|()| Some(out.cast::<u8>()).filter(|data| !data.is_null())),
        }
    }

    /// Every marker of the scene the server runs, in the scene's order.
    /// The loader reads them from the game's data once per scene, apart
    /// from the game thread, so a call after that is cheap.
    ///
    /// # Errors
    ///
    /// [`WorldError::NotLoaded`] while the loader still reads the scene,
    /// a second or two after the first call: ask again later;
    /// [`WorldError::WrongThread`] off the server's game thread or inside a
    /// step; [`WorldError::Missing`] while no world runs or when the scene
    /// does not read on this game build.
    ///
    /// # Examples
    ///
    /// See [`MapMarker`].
    pub fn markers(self) -> Result<Vec<MapMarker>, WorldError> {
        self.scan(|_| true)
    }

    /// The markers whose position lies in the box between corners `a` and
    /// `b`, in metres, edges included.
    ///
    /// # Errors
    ///
    /// [`WorldError::Refused`] for a corner that is not finite; otherwise
    /// as [`Map::markers`].
    ///
    /// # Examples
    ///
    /// ```
    /// use enshwick::map::Map;
    ///
    /// fn near_spawn(map: Map) -> usize {
    ///     map.markers_in([-500.0, -100.0, -500.0], [500.0, 400.0, 500.0])
    ///         .map_or(0, |markers| markers.len())
    /// }
    /// ```
    pub fn markers_in(self, a: [f64; 3], b: [f64; 3]) -> Result<Vec<MapMarker>, WorldError> {
        let (low, high) = crate::world::span(a, b).ok_or(WorldError::Refused)?;
        self.scan(|at| (0..3).all(|axis| (low[axis]..=high[axis]).contains(&at[axis])))
    }

    fn scan(self, keep: impl Fn([f64; 3]) -> bool) -> Result<Vec<MapMarker>, WorldError> {
        let host = self.0.host;
        let size = u32::try_from(size_of::<MapMarkerInfo>()).map_err(|_| WorldError::Missing)?;
        let mut capacity = 1024u32;
        let infos = loop {
            let mut infos = vec![MapMarkerInfo::EMPTY; capacity as usize];
            let mut count = 0u32;
            let status = (host.api().map_markers)(
                host.handle(),
                infos.as_mut_ptr(),
                size,
                capacity,
                &raw mut count,
            );
            WorldError::from_status(status)?;
            if count <= capacity {
                infos.truncate(count as usize);
                break infos;
            }
            capacity = count;
        };
        Ok(infos
            .iter()
            .filter(|info| keep(info.position))
            .map(|info| MapMarker {
                position: info.position,
                marker_type: set(info.marker_type).map(MapMarkerTypeId::from_guid),
                label: set(info.label),
            })
            .collect())
    }

    /// The frame of the map of the scene the server runs: where the map
    /// starts, how far it reaches and the size of its cells, in metres. The
    /// loader reads it from the game's data once per scene, apart from the
    /// game thread, as it reads the markers.
    ///
    /// # Errors
    ///
    /// [`WorldError::NotLoaded`] while the loader still reads the scene:
    /// ask again later; [`WorldError::WrongThread`] off the server's game
    /// thread or inside a step; [`WorldError::Missing`] while no world
    /// runs, for a scene without a map, or when the scene does not read on
    /// this game build.
    ///
    /// # Examples
    ///
    /// ```
    /// use enshwick::map::Map;
    ///
    /// fn cells(map: Map) -> Option<[f64; 2]> {
    ///     let frame = map.frame().ok()?;
    ///     Some(frame.size.map(|metres| metres / frame.metres_per_cell))
    /// }
    /// ```
    pub fn frame(self) -> Result<MapFrame, WorldError> {
        let host = self.0.host;
        let mut info = MapFrameInfo::EMPTY;
        WorldError::from_status((host.api().map_frame)(host.handle(), &raw mut info))?;
        MapFrame::from_info(&info).ok_or(WorldError::Missing)
    }

    /// Where `player` has set the waypoint all players see, in metres;
    /// `None` while it is off or for an entity without one. The server
    /// keeps it only while the player plays.
    ///
    /// # Errors
    ///
    /// [`WorldError::WrongThread`] off the server's game thread or inside a
    /// step; [`WorldError::Missing`] before a world runs or on a game build
    /// without waypoints.
    ///
    /// # Examples
    ///
    /// See the [module](self).
    pub fn waypoint(self, player: Entity) -> Result<Option<[f64; 3]>, WorldError> {
        let waypoint = self.0.waypoint.as_ref().ok_or(WorldError::Missing)?;
        let host = self.0.host;
        WorldError::from_status((host.api().world_quiet)(host.handle()))?;
        let Some(data) = self.live(player, waypoint.ty)? else {
            return Ok(None);
        };
        // SAFETY: `data` is the player's live PlayerWaypoint, read right
        // after `world_quiet` answered that this is the game thread at a
        // quiet moment, so no system writes it now; `resolve` checked that
        // the byte at `enabled_at` lies inside the type's size. It is read as
        // a byte, never as a bool.
        let enabled = unsafe { crate::raw::read_at::<u8>(data, waypoint.enabled_at) };
        if enabled == 0 {
            return Ok(None);
        }
        // SAFETY: `data` is the player's live PlayerWaypoint, read right
        // after `world_quiet` answered that this is the game thread at a
        // quiet moment, so no system writes it now; `resolve` checked that
        // 24 bytes from `position_at`, the three i64 of `waypointPosition`
        // in 32.32 fixed point, lie inside the type.
        let fixed = unsafe { crate::raw::read_at::<[i64; 3]>(data, waypoint.position_at) };
        Ok(Some(crate::ecs::metres(fixed)))
    }
}

impl Map {
    /// What the map shows now as a [`MapSnapshot`]: every player with
    /// position and waypoint, the Flame Altars of `altars` with their
    /// bases, every marker and the map's frame. Without `altars` the
    /// snapshot has none; without a frame its `frame` is `None`; on
    /// a game build without waypoints no player has one.
    /// The loader reads every marker, so take one now and then, not every
    /// step.
    ///
    /// # Errors
    ///
    /// As [`Map::markers`].
    ///
    /// # Examples
    ///
    /// ```
    /// use core::ops::ControlFlow;
    /// use std::time::Duration;
    ///
    /// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
    ///     let map = load.map()?;
    ///     let altars = load.altars()?;
    ///     load.scheduler()?.every(Duration::from_secs(5), move || {
    ///         if let Ok(bytes) = map.snapshot(Some(altars)).map(|s| s.to_bytes()) {
    ///             enshwick::debug!("snapshot of {} bytes", bytes.map_or(0, |b| b.len()));
    ///         }
    ///         ControlFlow::Continue(())
    ///     });
    ///     Ok(())
    /// }
    /// ```
    pub fn snapshot(self, altars: Option<Altars>) -> Result<MapSnapshot, WorldError> {
        let world = self.0.world;
        let frame = match self.frame() {
            Err(WorldError::Missing) => Ok(None),
            found => found.map(Some),
        };
        let markers = self.markers();
        let frame = frame?;
        let markers = markers?
            .into_iter()
            .map(|marker| MapPoint {
                position: marker.position,
                marker_type: marker.marker_type.map(MapMarkerTypeId::get),
                label: marker.label.map(Guid::fnv1a),
            })
            .collect();
        let mut online = world.players();
        online.sort_unstable_by_key(|player| player.slot);
        let mut players = Vec::with_capacity(online.len());
        for player in online {
            let waypoint = match self.waypoint(player.entity) {
                Err(WorldError::Missing) => None,
                found => found?,
            };
            players.push(MapPlayer {
                key: player.key,
                position: world.position(player.entity),
                waypoint,
                name: player.name,
            });
        }
        Ok(MapSnapshot {
            players,
            altars: altars.map(snapshot_altars).unwrap_or_default(),
            markers,
            frame,
        })
    }
}

/// The altars of `altars`, by entity, each with its base's number.
fn snapshot_altars(altars: Altars) -> Vec<MapAltar> {
    let mut list = altars.list();
    list.sort_unstable_by_key(|altar| altar.entity.0);
    let bases = crate::altars::bases_of(&list);
    list.iter()
        .map(|altar| MapAltar {
            position: altar.position,
            level: altar.level,
            zone: [altar.zone.min, altar.zone.max],
            base: bases
                .iter()
                .position(|base| base.altars.contains(&altar.entity))
                .and_then(|index| u32::try_from(index).ok()),
        })
        .collect()
}

/// `guid`, unless it is all zero, which the game writes for none.
fn set(guid: [u8; 16]) -> Option<Guid> {
    (guid != [0; 16]).then_some(Guid(guid))
}