#[cfg(feature = "unstable")]
use core::ffi::c_void;
#[cfg(feature = "unstable")]
use crate::Off;
use crate::WorldError;
pub use crate::abi::KnowledgeId;
#[cfg(feature = "unstable")]
use crate::abi::KnowledgeInput;
use crate::abi::{Header, KnowledgeInfo, Status};
use crate::{Entity, PlayerKey};
use crate::{Host, Load};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KnowledgeType {
Bool,
Counter,
Flags,
Other(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Scope {
World,
Player,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Known {
pub id: KnowledgeId,
pub r#type: KnowledgeType,
pub scope: Scope,
pub value: u32,
pub expected_flags: u32,
}
impl Known {
#[must_use]
pub const fn is_set(&self) -> bool {
self.value != 0
}
#[must_use]
pub const fn has_flag(&self, bit: u32) -> bool {
bit < 32 && self.value & (1 << bit) != 0
}
fn from_info(info: &KnowledgeInfo) -> Self {
Self {
id: info.id,
r#type: kind_of(info.kind),
scope: if info.scope == KnowledgeInfo::WORLD {
Scope::World
} else {
Scope::Player
},
value: info.value,
expected_flags: info.flags,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Grant {
Set,
Add(u32),
Flag(u32),
}
#[cfg(feature = "unstable")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Revoke {
Clear,
Sub(u32),
Unflag(u32),
}
#[cfg(feature = "unstable")]
impl Revoke {
const fn raw(self) -> (u32, u32) {
match self {
Self::Clear => (KnowledgeInfo::BOOL as u32, 0),
Self::Sub(amount) => (KnowledgeInfo::COUNTER as u32, amount),
Self::Unflag(bit) => (KnowledgeInfo::FLAGS as u32, bit),
}
}
}
impl Grant {
const fn raw(self) -> (u32, u32) {
match self {
Self::Set => (KnowledgeInfo::BOOL as u32, 0),
Self::Add(amount) => (KnowledgeInfo::COUNTER as u32, amount),
Self::Flag(bit) => (KnowledgeInfo::FLAGS as u32, bit),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Knowledge {
host: Host,
}
impl Knowledge {
fn read(&self, entity: Entity, id: KnowledgeId) -> Option<Known> {
let mut info = KnowledgeInfo {
header: Header::of::<KnowledgeInfo>(),
id: KnowledgeId::default(),
kind: 0,
scope: 0,
reserved: 0,
value: 0,
flags: 0,
};
let status = (self.host.api().knowledge)(self.host.handle(), entity, id, &raw mut info);
(status == Status::OK).then(|| Known::from_info(&info))
}
#[must_use]
pub fn get(&self, player: Entity, id: KnowledgeId) -> Option<Known> {
self.read(player, id)
}
#[must_use]
pub fn world(&self, id: KnowledgeId) -> Option<Known> {
self.read(Entity::NONE, id)
}
pub fn grant(&self, player: Entity, id: KnowledgeId, grant: Grant) -> Result<(), WorldError> {
let (kind, value) = grant.raw();
WorldError::from_status((self.host.api().grant_knowledge)(
self.host.handle(),
player,
id,
kind,
value,
))
}
pub fn grant_world(&self, id: KnowledgeId, grant: Grant) -> Result<(), WorldError> {
self.grant(Entity(0), id, grant)
}
#[cfg(feature = "unstable")]
pub fn revoke(
&self,
player: Entity,
id: KnowledgeId,
revoke: Revoke,
) -> Result<(), WorldError> {
let (kind, value) = revoke.raw();
WorldError::from_status((self.host.api().revoke_knowledge)(
self.host.handle(),
player,
id,
kind,
value,
))
}
#[cfg(feature = "unstable")]
pub fn revoke_world(&self, id: KnowledgeId, revoke: Revoke) -> Result<(), WorldError> {
self.revoke(Entity(0), id, revoke)
}
pub fn grant_account(
&self,
account: PlayerKey,
id: KnowledgeId,
grant: Grant,
) -> Result<(), WorldError> {
let (kind, value) = grant.raw();
self.for_account(account, id, kind, value, false)
}
#[cfg(feature = "unstable")]
pub fn revoke_account(
&self,
account: PlayerKey,
id: KnowledgeId,
revoke: Revoke,
) -> Result<(), WorldError> {
let (kind, value) = revoke.raw();
self.for_account(account, id, kind, value, true)
}
fn for_account(
&self,
account: PlayerKey,
id: KnowledgeId,
kind: u32,
value: u32,
revoke: bool,
) -> Result<(), WorldError> {
WorldError::from_status((self.host.api().account_knowledge)(
self.host.handle(),
account,
id,
kind,
value,
u32::from(revoke),
))
}
#[must_use]
pub fn quests(&self) -> Vec<KnowledgeId> {
let mut ids = vec![KnowledgeId::default(); 256];
loop {
let mut count = 0u32;
let capacity = u32::try_from(ids.len()).unwrap_or(u32::MAX);
let status = (self.host.api().quests)(
self.host.handle(),
ids.as_mut_ptr(),
capacity,
&raw mut count,
);
if status != Status::OK {
return Vec::new();
}
let count = count as usize;
if count <= ids.len() {
ids.truncate(count);
return ids;
}
ids = vec![KnowledgeId::default(); count];
}
}
#[must_use]
pub fn completed(&self, player: Entity, quest: KnowledgeId) -> bool {
self.get(player, quest).is_some_and(|known| known.is_set())
}
}
#[cfg(feature = "unstable")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Refusal {
Unknown,
Kind,
World,
Counter,
FlagBit,
Experience,
SkillPoints,
Quest,
Other(u32),
}
#[cfg(feature = "unstable")]
impl Refusal {
const fn from_raw(raw: u32) -> Option<Self> {
Some(match raw {
KnowledgeInput::ALLOWED => return None,
KnowledgeInput::UNKNOWN => Self::Unknown,
KnowledgeInput::KIND => Self::Kind,
KnowledgeInput::WORLD => Self::World,
KnowledgeInput::COUNTER => Self::Counter,
KnowledgeInput::FLAG_BIT => Self::FlagBit,
KnowledgeInput::EXPERIENCE => Self::Experience,
KnowledgeInput::SKILL_POINTS => Self::SkillPoints,
KnowledgeInput::QUEST => Self::Quest,
other => Self::Other(other),
})
}
}
#[cfg(feature = "unstable")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct KnowledgeRequest {
pub player: Entity,
pub id: KnowledgeId,
pub kind: KnowledgeType,
pub scope: Option<Scope>,
pub value: u32,
pub refusal: Option<Refusal>,
pub allowed: bool,
}
#[cfg(feature = "unstable")]
unsafe extern "system" fn run_input<F>(call: *const KnowledgeInput, user: *mut c_void) -> u8
where
F: Fn(&KnowledgeRequest) -> bool,
{
let Some(call) = (unsafe { call.as_ref() }) else {
return 0;
};
let allowed = call.allowed != 0;
let request = KnowledgeRequest {
player: call.player,
id: call.id,
kind: kind_of(call.kind),
scope: match call.scope {
KnowledgeInfo::WORLD => Some(Scope::World),
KnowledgeInfo::PLAYER => Some(Scope::Player),
_ => None,
},
value: call.value,
refusal: Refusal::from_raw(call.refusal),
allowed,
};
let filter = unsafe { &*user.cast::<F>() };
u8::from(crate::host::guard(
"panic in a knowledge input filter",
|| allowed,
|| filter(&request),
))
}
const fn kind_of(raw: u8) -> KnowledgeType {
match raw {
KnowledgeInfo::BOOL => KnowledgeType::Bool,
KnowledgeInfo::COUNTER => KnowledgeType::Counter,
KnowledgeInfo::FLAGS => KnowledgeType::Flags,
other => KnowledgeType::Other(other),
}
}
impl Load<'_> {
#[cfg(feature = "unstable")]
pub fn on_knowledge_input<F>(&mut self, filter: F) -> Result<(), Off>
where
F: Fn(&KnowledgeRequest) -> bool + Send + Sync + 'static,
{
let host = self.host();
crate::load::hand_over(
filter,
|user| (host.api().knowledge_input_filter)(host.handle(), run_input::<F>, user),
"knowledge input filter",
)
}
#[must_use]
pub fn knowledge(&mut self) -> Knowledge {
Knowledge { host: self.host() }
}
}