use core::time::Duration;
use crate::abi::{ActiveBuff, BuffRequest, BuffTypeInfo, Header, Status};
use crate::ecs::Entity;
use crate::world::WorldError;
use crate::{Host, Load};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BuffId(pub u32);
impl BuffId {
#[must_use]
pub const fn from_guid(guid: [u8; 16]) -> Self {
Self(crate::content::Guid(guid).id())
}
#[must_use]
pub const fn named(id: &str) -> Self {
Self::from_guid(crate::content::Guid::named(id).0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BuffKind {
Food,
Buff,
Debuff,
Rested,
Terrain,
Other(u16),
}
impl BuffKind {
const fn from_slot(slot: u16) -> Self {
match slot {
BuffTypeInfo::FOOD => Self::Food,
BuffTypeInfo::BUFF => Self::Buff,
BuffTypeInfo::DEBUFF => Self::Debuff,
BuffTypeInfo::RESTED => Self::Rested,
BuffTypeInfo::TERRAIN => Self::Terrain,
other => Self::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct BuffType {
pub id: BuffId,
pub kind: BuffKind,
pub duration: Duration,
pub saved: bool,
pub hidden: bool,
pub name: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Buff {
pub id: BuffId,
pub entity: Entity,
pub kind: BuffKind,
pub level: Option<u32>,
pub duration: Duration,
pub remaining: Duration,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Apply {
pub duration: Option<Duration>,
pub level: Option<u8>,
}
impl Apply {
#[must_use]
pub const fn new() -> Self {
Self {
duration: None,
level: None,
}
}
#[must_use]
pub const fn lasting(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
#[must_use]
pub const fn level(mut self, level: u8) -> Self {
self.level = Some(level);
self
}
}
#[derive(Clone, Copy, Debug)]
pub struct Buffs {
host: Host,
}
fn nanos(duration: i64) -> Duration {
Duration::from_nanos(u64::try_from(duration).unwrap_or(0))
}
impl Buffs {
#[must_use]
pub fn types(&self) -> Vec<BuffType> {
let blank = BuffTypeInfo {
header: Header::of::<BuffTypeInfo>(),
id: 0,
slot: 0,
flags: 0,
name: 0,
duration: 0,
};
let fill = |out: *mut BuffTypeInfo, capacity: u32, count: *mut u32| {
(self.host.api().buff_types)(self.host.handle(), out, capacity, count)
};
list(blank, fill)
.unwrap_or_default()
.iter()
.map(|info| BuffType {
id: BuffId(info.id),
kind: BuffKind::from_slot(info.slot),
duration: nanos(info.duration),
saved: info.flags & BuffTypeInfo::SAVED != 0,
hidden: info.flags & BuffTypeInfo::HIDDEN != 0,
name: info.name,
})
.collect()
}
pub fn of(&self, entity: Entity) -> Result<Vec<Buff>, WorldError> {
let blank = ActiveBuff {
header: Header::of::<ActiveBuff>(),
id: 0,
entity: 0,
slot: 0,
level: 0,
duration: 0,
remaining: 0,
};
let fill = |out: *mut ActiveBuff, capacity: u32, count: *mut u32| {
(self.host.api().buffs)(self.host.handle(), entity.0, out, capacity, count)
};
Ok(list(blank, fill)?
.iter()
.map(|buff| Buff {
id: BuffId(buff.id),
entity: Entity(buff.entity),
kind: BuffKind::from_slot(u16::try_from(buff.slot).unwrap_or(u16::MAX)),
level: (buff.level != 0).then_some(buff.level),
duration: nanos(buff.duration),
remaining: nanos(buff.remaining),
})
.collect())
}
#[must_use]
pub fn has(&self, entity: Entity, id: BuffId) -> bool {
self.of(entity)
.is_ok_and(|on| on.iter().any(|buff| buff.id == id))
}
pub fn apply(&self, entity: Entity, id: BuffId, how: Apply) -> Result<(), WorldError> {
let duration = how.duration.map_or(0, |duration| {
i64::try_from(duration.as_nanos())
.unwrap_or(i64::MAX)
.max(1)
});
let request = BuffRequest {
header: Header::of::<BuffRequest>(),
entity: entity.0,
id: id.0,
level: how.level.map_or(0, u32::from),
reserved: 0,
duration,
};
WorldError::check(
(self.host.api().apply_buff)(self.host.handle(), &raw const request),
WorldError::Lacks,
)
}
pub fn remove(&self, entity: Entity, id: BuffId) -> Result<bool, WorldError> {
let mut removed = 0u8;
WorldError::check(
(self.host.api().remove_buff)(self.host.handle(), entity.0, id.0, &raw mut removed),
WorldError::Lacks,
)?;
Ok(removed != 0)
}
}
fn list<T: Copy>(
blank: T,
fill: impl Fn(*mut T, u32, *mut u32) -> Status,
) -> Result<Vec<T>, WorldError> {
let mut items = vec![blank; 64];
loop {
let mut count = 0u32;
let capacity = u32::try_from(items.len()).unwrap_or(u32::MAX);
WorldError::check(
fill(items.as_mut_ptr(), capacity, &raw mut count),
WorldError::Lacks,
)?;
let count = count as usize;
if count <= items.len() {
items.truncate(count);
return Ok(items);
}
items = vec![blank; count];
}
}
impl Load<'_> {
#[must_use]
pub fn buffs(&mut self) -> Buffs {
Buffs { host: self.host() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_guid_hashes_as_the_game_hashes_it() {
assert_eq!(BuffId::from_guid([0; 16]), BuffId(0x6969_1905));
}
}