use crate::abi::{Header, SpawnRequest, TeleportRequest};
use crate::ecs::Entity;
use crate::game_events::{EventType, Field, Scalar};
use crate::world::WorldError;
use crate::{Host, Load, Off};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Teleport {
pub best_spawn: bool,
pub facing: Option<[f32; 4]>,
}
impl Teleport {
#[must_use]
pub const fn new() -> Self {
Self {
best_spawn: false,
facing: None,
}
}
#[must_use]
pub const fn best_spawn(mut self) -> Self {
self.best_spawn = true;
self
}
#[must_use]
pub fn facing_yaw(mut self, yaw: f32) -> Self {
let half = yaw / 2.0;
self.facing = Some([0.0, half.sin(), 0.0, half.cos()]);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Spawn {
pub level: Option<u32>,
pub despawn_after: Option<core::time::Duration>,
pub facing: Option<[f32; 4]>,
}
impl Spawn {
#[must_use]
pub const fn new() -> Self {
Self {
level: None,
despawn_after: None,
facing: None,
}
}
#[must_use]
pub const fn level(mut self, level: u32) -> Self {
self.level = Some(level);
self
}
#[must_use]
pub const fn despawn_after(mut self, time: core::time::Duration) -> Self {
self.despawn_after = Some(time);
self
}
#[must_use]
pub fn facing_yaw(mut self, yaw: f32) -> Self {
let half = yaw / 2.0;
self.facing = Some([0.0, half.sin(), 0.0, half.cos()]);
self
}
}
#[derive(Clone, Copy, Debug)]
struct TransformAt {
position: u64,
orientation: u64,
scale: u64,
}
#[derive(Clone, Copy, Debug)]
struct NpcCommand {
ty: EventType,
kind: Field<u8>,
npc: Field<u32>,
target: TransformAt,
}
#[derive(Clone, Copy, Debug)]
struct SetToggle {
ty: EventType,
sender: Field<Entity>,
signal: Field<u32>,
state: Field<bool>,
}
#[derive(Clone, Copy, Debug)]
struct AddThreat {
ty: EventType,
attacker: Field<Entity>,
target: Field<Entity>,
threat: Field<f32>,
}
#[derive(Clone, Copy, Debug)]
struct Ping {
ty: EventType,
sender: Field<Entity>,
position: u64,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum NpcOrder {
GoTo([f64; 3]),
Reset,
}
#[derive(Clone, Copy, Debug)]
pub struct Effects {
host: Host,
npc: Option<NpcCommand>,
toggle: Option<SetToggle>,
threat: Option<AddThreat>,
ping: Option<Ping>,
}
impl Effects {
pub(crate) fn resolve(load: &Load<'_>) -> Self {
let host = load.host();
let transform = |base: u64| -> Result<TransformAt, Off> {
Ok(TransformAt {
position: base + load.field_offset("keen::WorldTransform", "position")?,
orientation: base + load.field_offset("keen::WorldTransform", "orientation")?,
scale: base + load.field_offset("keen::WorldTransform", "scale")?,
})
};
let npc = (|| {
let ty = load.event_type("keen::ecs::NpcCommandEvent")?;
Ok::<_, Off>(NpcCommand {
ty,
kind: ty.field(load, "type")?,
npc: ty.field(load, "npcId")?,
target: transform(load.field_offset(ty.name(), "targetTransform")?)?,
})
})();
let toggle = (|| {
let ty = load.event_type("keen::ecs::SetToggleStateEvent")?;
Ok::<_, Off>(SetToggle {
ty,
sender: ty.field(load, "senderId")?,
signal: ty.field(load, "signalId")?,
state: ty.field(load, "newState")?,
})
})();
let threat = (|| {
let ty = load.event_type("keen::ecs::AddThreatTargetEvent")?;
Ok::<_, Off>(AddThreat {
ty,
attacker: ty.field(load, "attackerId")?,
target: ty.field(load, "targetId")?,
threat: ty.field(load, "threatValue")?,
})
})();
let ping = (|| {
let ty = load.event_type("keen::ecs::UiPingEvent")?;
Ok::<_, Off>(Ping {
ty,
sender: ty.field(load, "senderEntityId")?,
position: load.field_offset(ty.name(), "position")?,
})
})();
Self {
host,
npc: keep("survivor orders", npc),
toggle: keep("toggles", toggle),
threat: keep("threat", threat),
ping: keep("pings", ping),
}
}
pub fn npc_command(&self, npc: u32, order: NpcOrder) -> Result<(), WorldError> {
let event = self.npc.ok_or(WorldError::Missing)?;
let mut bytes = blank(event.ty)?;
let (kind, at) = match order {
NpcOrder::Reset => (0, None),
NpcOrder::GoTo(at) => (1, Some(at)),
};
put(&mut bytes, event.kind, kind)?;
put(&mut bytes, event.npc, npc)?;
put_quaternion(&mut bytes, event.target.orientation, [0.0, 0.0, 0.0, 1.0])?;
put_floats(&mut bytes, event.target.scale, [1.0; 3])?;
if let Some(at) = at {
put_position(&mut bytes, event.target.position, at)?;
}
self.emit_raw(event.ty, &bytes)
}
pub fn set_toggle(&self, sender: Entity, signal: u32, on: bool) -> Result<(), WorldError> {
let event = self.toggle.ok_or(WorldError::Missing)?;
let mut bytes = blank(event.ty)?;
put(&mut bytes, event.sender, sender)?;
put(&mut bytes, event.signal, signal)?;
put(&mut bytes, event.state, on)?;
self.emit_raw(event.ty, &bytes)
}
pub fn add_threat(
&self,
attacker: Entity,
target: Entity,
threat: f32,
) -> Result<(), WorldError> {
let event = self.threat.ok_or(WorldError::Missing)?;
let mut bytes = blank(event.ty)?;
put(&mut bytes, event.attacker, attacker)?;
put(&mut bytes, event.target, target)?;
put(&mut bytes, event.threat, threat)?;
self.emit_raw(event.ty, &bytes)
}
pub fn ping(&self, sender: Entity, position: [f64; 3]) -> Result<(), WorldError> {
let event = self.ping.ok_or(WorldError::Missing)?;
let mut bytes = blank(event.ty)?;
put(&mut bytes, event.sender, sender)?;
put_position(&mut bytes, event.position, position)?;
self.emit_raw(event.ty, &bytes)
}
pub fn emit_raw(&self, ty: EventType, bytes: &[u8]) -> Result<(), WorldError> {
let size = u32::try_from(bytes.len()).map_err(|_| WorldError::Refused)?;
let status = (self.host.api().emit)(self.host.handle(), ty.hash(), bytes.as_ptr(), size);
WorldError::from_status(status)
}
pub fn teleport(
&self,
entity: Entity,
position: [f64; 3],
how: Teleport,
) -> Result<(), WorldError> {
let mut flags = 0;
if how.best_spawn {
flags |= TeleportRequest::BEST_SPAWN;
}
if how.facing.is_some() {
flags |= TeleportRequest::ORIENTED;
}
let request = TeleportRequest {
header: Header::of::<TeleportRequest>(),
entity: entity.0,
flags,
position: crate::ecs::fixed(position),
orientation: how.facing.unwrap_or([0.0, 0.0, 0.0, 1.0]),
};
WorldError::check(
(self.host.api().teleport)(self.host.handle(), &raw const request),
WorldError::Gone,
)
}
}
impl Effects {
pub fn spawn(
&self,
template: [u8; 16],
position: [f64; 3],
how: Spawn,
) -> Result<Entity, WorldError> {
let api = self.host.api();
let lifetime = how.despawn_after.map_or(0, |time| {
u64::try_from(time.as_nanos()).unwrap_or(u64::MAX).max(1)
});
let request = SpawnRequest {
header: Header::of::<SpawnRequest>(),
template,
position: crate::ecs::fixed(position),
orientation: how.facing.unwrap_or([0.0, 0.0, 0.0, 1.0]),
level: how.level.unwrap_or(0),
reserved: 0,
lifetime_ns: lifetime,
};
let mut entity = 0u32;
WorldError::from_status((api.spawn)(
self.host.handle(),
&raw const request,
&raw mut entity,
))?;
Ok(Entity(entity))
}
}
fn keep<T>(what: &str, found: Result<T, Off>) -> Option<T> {
found
.inspect_err(|off| crate::debug!("{what} off: {off}"))
.ok()
}
fn blank(ty: EventType) -> Result<Vec<u8>, WorldError> {
usize::try_from(ty.size())
.map(|size| vec![0u8; size])
.map_err(|_| WorldError::Missing)
}
fn put<T: Scalar>(bytes: &mut [u8], field: Field<T>, value: T) -> Result<(), WorldError> {
put_at(bytes, u64::from(field.offset()), value)
}
fn put_at<T: Scalar>(bytes: &mut [u8], at: u64, value: T) -> Result<(), WorldError> {
let written = usize::try_from(at)
.ok()
.and_then(|at| bytes.get_mut(at..))
.is_some_and(|out| value.to_bytes(out));
written.then_some(()).ok_or(WorldError::Missing)
}
fn put_position(bytes: &mut [u8], at: u64, position: [f64; 3]) -> Result<(), WorldError> {
for (i, axis) in crate::ecs::fixed(position).into_iter().enumerate() {
put_at(bytes, at + 8 * i as u64, axis)?;
}
Ok(())
}
fn put_floats<const N: usize>(
bytes: &mut [u8],
at: u64,
values: [f32; N],
) -> Result<(), WorldError> {
for (i, value) in values.into_iter().enumerate() {
put_at(bytes, at + 4 * i as u64, value)?;
}
Ok(())
}
fn put_quaternion(bytes: &mut [u8], at: u64, value: [f32; 4]) -> Result<(), WorldError> {
put_floats(bytes, at, value)
}
impl Load<'_> {
#[must_use]
pub fn effects(&mut self) -> Effects {
Effects::resolve(self)
}
}