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};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MapMarkerTypeId(u32);
impl MapMarkerTypeId {
#[must_use]
pub const fn new(id: u32) -> Self {
Self(id)
}
#[must_use]
pub const fn from_guid(reference: Guid) -> Self {
Self(reference.fnv1a())
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MapMarker {
pub position: [f64; 3],
pub marker_type: Option<MapMarkerTypeId>,
pub label: Option<Guid>,
}
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MapFrame {
pub origin: [f64; 2],
pub size: [f64; 2],
pub metres_per_cell: f64,
}
impl MapFrame {
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,
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct Map(&'static Resolved);
#[derive(Debug)]
struct Resolved {
host: Host,
world: World,
waypoint: Option<Waypoint>,
}
#[derive(Debug)]
struct Waypoint {
ty: ComponentType,
enabled_at: usize,
position_at: usize,
}
static RESOLVED: crate::state::ModLocal<Resolved> = crate::state::ModLocal::new();
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)))
}
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())),
}
}
pub fn markers(self) -> Result<Vec<MapMarker>, WorldError> {
self.scan(|_| true)
}
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())
}
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)
}
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);
};
let enabled = unsafe { crate::raw::read_at::<u8>(data, waypoint.enabled_at) };
if enabled == 0 {
return Ok(None);
}
let fixed = unsafe { crate::raw::read_at::<[i64; 3]>(data, waypoint.position_at) };
Ok(Some(crate::ecs::metres(fixed)))
}
}
impl Map {
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,
})
}
}
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()
}
fn set(guid: [u8; 16]) -> Option<Guid> {
(guid != [0; 16]).then_some(Guid(guid))
}