use bytemuck::Zeroable;
use rustc_hash::FxHashMap;
use crate::Entity;
use crate::ItemId;
use crate::abi::{EntityPlace, Header, ItemData};
use super::{World, WorldError};
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WorldTransform {
pub position: [f64; 3],
pub orientation: [f32; 4],
}
impl WorldTransform {
pub const UPRIGHT: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
#[must_use]
pub const fn at(position: [f64; 3]) -> Self {
Self {
position,
orientation: Self::UPRIGHT,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Prop {
pub entity: Entity,
pub item: ItemId,
pub transform: WorldTransform,
}
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CapturedProp {
pub item: ItemId,
pub transform: WorldTransform,
}
impl From<Prop> for CapturedProp {
fn from(prop: Prop) -> Self {
Self {
item: prop.item,
transform: prop.transform,
}
}
}
pub(crate) fn span(a: [f64; 3], b: [f64; 3]) -> Option<([f64; 3], [f64; 3])> {
if a.iter().chain(&b).any(|axis| !axis.is_finite()) {
return None;
}
let low = [0, 1, 2].map(|axis| a[axis].min(b[axis]));
let high = [0, 1, 2].map(|axis| a[axis].max(b[axis]));
Some((low, high))
}
impl World {
pub fn props_in(self, a: [f64; 3], b: [f64; 3]) -> Result<Vec<Prop>, WorldError> {
let (low, high) = span(a, b).ok_or(WorldError::Refused)?;
let orientation = self.0.orientation.ok_or(WorldError::Missing)?;
let no_props = self.0.no_props.ok_or(WorldError::Missing)?;
let mut templates = Templates::default();
let mut props: Vec<Prop> = self
.placed_with(self.0.used_item.hash())?
.iter()
.map(|place| (place.entity, crate::ecs::metres(place.position)))
.filter(|(_, at)| (0..3).all(|axis| (low[axis]..=high[axis]).contains(&at[axis])))
.filter_map(|(entity, position)| {
if no_props
.iter()
.any(|&marks| self.component(entity, marks).is_some())
{
return None;
}
let item = self.used_item(entity)?;
let placed = templates.placed_by(self, item)?;
if self.template(entity) != Some(placed) {
return None;
}
let data = self.component(entity, self.0.transform)?;
let orientation =
unsafe { crate::raw::read_at::<[f32; 4]>(data.cast(), orientation) };
Some(Prop {
entity,
item,
transform: WorldTransform {
position,
orientation,
},
})
})
.collect();
if templates.found == 0 && templates.unknown > 0 {
return Err(WorldError::Missing);
}
props.sort_unstable_by_key(|prop| prop.entity.0);
Ok(props)
}
pub(crate) fn placed_with(self, hash: u32) -> Result<Vec<EntityPlace>, WorldError> {
let entities = self.0.host.api().entities;
let mut capacity = 1024u32;
loop {
let mut places = vec![EntityPlace::default(); capacity as usize];
let mut count = 0u32;
let status = entities(
self.0.host.handle(),
hash,
places.as_mut_ptr(),
capacity,
&raw mut count,
);
WorldError::from_status(status)?;
if count <= capacity {
places.truncate(count as usize);
return Ok(places);
}
capacity = count;
}
}
}
#[derive(Default)]
struct Templates {
known: FxHashMap<ItemId, Option<[u8; 16]>>,
found: usize,
unknown: usize,
}
impl Templates {
fn placed_by(&mut self, world: World, item: ItemId) -> Option<[u8; 16]> {
*self.known.entry(item).or_insert_with(|| {
let mut out = ItemData::zeroed();
out.header = Header::of::<ItemData>();
let host = world.0.host;
if (host.api().item_info)(host.handle(), item, &raw mut out).is_ok() {
self.found += 1;
Some(out.placed_entity).filter(|guid| *guid != [0; 16])
} else {
self.unknown += 1;
None
}
})
}
}