use crate::ecs::Entity;
use crate::engine_ids as ids;
use crate::game_events::{EventType, Field, RawGameEvent};
use crate::raw::read_at;
use crate::world::{ComponentType, World};
use crate::{Load, Off};
mod sealed {
pub trait Sealed {}
}
pub trait Kind: sealed::Sealed + Sized + 'static {
#[doc(hidden)]
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static;
}
fn some(entity: Entity) -> Option<Entity> {
(!entity.is_none()).then_some(entity)
}
fn on<E, D, F>(load: &mut Load<'_>, ty: EventType, decode: D, handler: F) -> Result<(), Off>
where
D: Fn(&RawGameEvent<'_>) -> Option<E> + Send + Sync + 'static,
F: Fn(&E) + Send + Sync + 'static,
{
load.on_raw_game_event(ty, move |event| {
if let Some(typed) = decode(event) {
handler(&typed);
}
})
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct DamageTypes(pub u16);
impl DamageTypes {
pub const CUT: Self = Self(1);
pub const PIERCE: Self = Self(1 << 1);
pub const BLUNT: Self = Self(1 << 2);
pub const FIRE: Self = Self(1 << 3);
pub const ICE: Self = Self(1 << 4);
pub const SHROUD: Self = Self(1 << 5);
pub const LIGHTNING: Self = Self(1 << 6);
pub const HEALING: Self = Self(1 << 7);
pub const POISON: Self = Self(1 << 8);
pub const WOOD: Self = Self(1 << 9);
pub const STONE: Self = Self(1 << 10);
pub const METAL: Self = Self(1 << 11);
pub const EXPLOSION: Self = Self(1 << 12);
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct AttackCategories(pub u8);
impl AttackCategories {
pub const MELEE: Self = Self(1);
pub const RANGED: Self = Self(1 << 1);
pub const MAGIC: Self = Self(1 << 2);
pub const TOOL: Self = Self(1 << 3);
pub const OTHER: Self = Self(1 << 4);
pub const AREA: Self = Self(1 << 5);
pub const SKILL_SHOT: Self = Self(1 << 6);
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Hit {
pub attacker: Option<Entity>,
pub source: Option<Entity>,
pub target: Entity,
pub root_target: Option<Entity>,
pub weapon: Option<Entity>,
pub health_change: i32,
pub stamina_change: i32,
pub damage_types: DamageTypes,
pub attack: AttackCategories,
pub target_faction: u8,
}
impl sealed::Sealed for Hit {}
impl Kind for Hit {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::HIT_EVENT)?;
let attacker: Field<Entity> = ty.field_by_id(load, ids::HIT_ATTACKER)?;
let target: Field<Entity> = ty.field_by_id(load, ids::HIT_TARGET)?;
let source: Field<Entity> = ty.field_by_id(load, ids::HIT_SOURCE)?;
let root_target: Field<Entity> = ty.field_by_id(load, ids::HIT_ROOT_TARGET)?;
let weapon: Field<Entity> = ty.field_by_id(load, ids::HIT_WEAPON)?;
let health: Field<i32> = ty.field_by_id(load, ids::HIT_HEALTH_CHANGE)?;
let stamina: Field<i32> = ty.field_by_id(load, ids::HIT_STAMINA_CHANGE)?;
let damage: Field<u16> = ty.field_by_id(load, ids::HIT_DAMAGE_TYPES)?;
let attack: Field<u8> = ty.field_by_id(load, ids::HIT_ATTACK)?;
let faction: Field<u8> = ty.field_by_id(load, ids::HIT_TARGET_FACTION)?;
on(
load,
ty,
move |e| {
Some(Self {
attacker: some(e.get(attacker)?),
source: some(e.get(source)?),
target: e.get(target)?,
root_target: some(e.get(root_target)?),
weapon: some(e.get(weapon)?),
health_change: e.get(health)?,
stamina_change: e.get(stamina)?,
damage_types: DamageTypes(e.get(damage)?),
attack: AttackCategories(e.get(attack)?),
target_faction: e.get(faction)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Died {
pub entity: Entity,
pub killer: Option<Entity>,
pub killer_player: Option<Entity>,
}
impl sealed::Sealed for Died {}
#[derive(Clone, Copy, Debug)]
struct Credit {
world: World,
history: ComponentType,
last_index: usize,
entries: usize,
slots: usize,
entry_size: usize,
entry_attacker: usize,
experience: ComponentType,
last_player: usize,
}
impl Credit {
fn resolve(load: &mut Load<'_>) -> Result<Self, Off> {
let world = load.world()?;
let offset = |id: &str| {
load.constant(id).and_then(|o| {
usize::try_from(o).map_err(|_| Off::new(format!("{id}: out of range")))
})
};
let size = usize::try_from(load.type_size(ids::HIT_HISTORY)?).unwrap_or(0);
let source_size = usize::try_from(load.type_size(ids::EXPERIENCE_CREDIT)?).unwrap_or(0);
let entries = offset(ids::HIT_HISTORY_ENTRIES)?;
let entry_size = usize::try_from(load.type_size(ids::HIT_EVENT)?)
.unwrap_or(0)
.max(1);
let slots = offset(ids::HIT_HISTORY_END)?.saturating_sub(entries) / entry_size;
Ok(Self {
history: world.component_type(load, ids::HIT_HISTORY)?,
last_index: offset(ids::HIT_HISTORY_LAST_INDEX)?,
entries,
slots,
entry_size,
entry_attacker: offset(ids::HIT_ATTACKER)?,
experience: world.component_type(load, ids::EXPERIENCE_CREDIT)?,
last_player: offset(ids::EXPERIENCE_CREDIT_LAST_PLAYER)?,
world,
})
.and_then(|credit: Self| {
let id = size_of::<u32>();
(credit.last_index + id <= size
&& credit.entries + credit.slots * credit.entry_size <= size
&& credit.entry_attacker + id <= credit.entry_size
&& credit.last_player + id <= source_size)
.then_some(credit)
.ok_or_else(|| Off::new("kill credit: an unexpected layout"))
})
}
fn killer(&self, entity: Entity) -> Option<Entity> {
let history = self.world.component(entity, self.history)?.cast::<u8>();
let last: u32 = unsafe { read_at(history, self.last_index) };
let last = usize::try_from(last).ok().filter(|&i| i < self.slots)?;
let at = self.entries + last * self.entry_size + self.entry_attacker;
some(Entity(unsafe { read_at(history, at) }))
}
fn killer_player(&self, entity: Entity) -> Option<Entity> {
let source = self.world.component(entity, self.experience)?.cast::<u8>();
some(Entity(unsafe { read_at(source, self.last_player) }))
}
}
impl Kind for Died {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::DIED_EVENT)?;
let dead: Field<Entity> = ty.field_by_id(load, ids::DIED_ENTITY)?;
let credit = Credit::resolve(load)
.inspect_err(|off| crate::debug!("kill credit off: {off}"))
.ok();
on(
load,
ty,
move |e| {
let entity = e.get(dead)?;
Some(Self {
entity,
killer: credit.as_ref().and_then(|c| c.killer(entity)),
killer_player: credit.as_ref().and_then(|c| c.killer_player(entity)),
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct LevelGained {
pub player: Entity,
pub level: u32,
}
impl sealed::Sealed for LevelGained {}
impl Kind for LevelGained {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::LEVEL_GAINED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::LEVEL_GAINED_PLAYER)?;
let level: Field<i32> = ty.field_by_id(load, ids::LEVEL_GAINED_LEVEL)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
level: u32::try_from(e.get(level)?).ok()?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ExperienceSource {
None,
Exploration,
Lore,
Quest,
Combat,
Mining,
ItemSetCompleted,
Other(u8),
}
impl ExperienceSource {
#[must_use]
pub const fn from_raw(value: u8) -> Self {
match value {
0 => Self::None,
1 => Self::Exploration,
2 => Self::Lore,
3 => Self::Quest,
4 => Self::Combat,
5 => Self::Mining,
6 => Self::ItemSetCompleted,
other => Self::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct ExperienceGained {
pub player: Entity,
pub amount: u32,
pub source: ExperienceSource,
}
impl sealed::Sealed for ExperienceGained {}
impl Kind for ExperienceGained {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::EXPERIENCE_GAINED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::EXPERIENCE_GAINED_PLAYER)?;
let amount: Field<u32> = ty.field_by_id(load, ids::EXPERIENCE_GAINED_AMOUNT)?;
let source: Field<u8> = ty.field_by_id(load, ids::EXPERIENCE_GAINED_SOURCE)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
amount: e.get(amount)?,
source: ExperienceSource::from_raw(e.get(source)?),
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct CraftRequested {
pub player: Entity,
pub station: Option<Entity>,
pub recipe: u32,
pub amount: u16,
}
impl sealed::Sealed for CraftRequested {}
impl Kind for CraftRequested {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::CRAFT_REQUESTED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::CRAFT_REQUESTED_PLAYER)?;
let station: Field<Entity> = ty.field_by_id(load, ids::CRAFT_REQUESTED_STATION)?;
let recipe: Field<u32> = ty.field_by_id(load, ids::CRAFT_REQUESTED_RECIPE)?;
let amount: Field<u16> = ty.field_by_id(load, ids::CRAFT_REQUESTED_AMOUNT)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
station: some(e.get(station)?),
recipe: e.get(recipe)?,
amount: e.get(amount)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Crafted {
pub workshop: u32,
pub recipe: u32,
}
impl sealed::Sealed for Crafted {}
impl Kind for Crafted {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::CRAFTED_EVENT)?;
let workshop: Field<u32> = ty.field_by_id(load, ids::CRAFTED_WORKSHOP)?;
let recipe: Field<u32> = ty.field_by_id(load, ids::CRAFTED_RECIPE)?;
on(
load,
ty,
move |e| {
Some(Self {
workshop: e.get(workshop)?,
recipe: e.get(recipe)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Interacted {
pub player: Entity,
pub target: Entity,
pub offer: u32,
}
impl sealed::Sealed for Interacted {}
impl Kind for Interacted {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::INTERACTED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::INTERACTED_PLAYER)?;
let target: Field<Entity> = ty.field_by_id(load, ids::INTERACTED_TARGET)?;
let offer: Field<u32> = ty.field_by_id(load, ids::INTERACTED_OFFER)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
target: e.get(target)?,
offer: e.get(offer)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Block {
pub player: Entity,
pub position: [f32; 3],
pub volume_min: [f32; 3],
pub volume_max: [f32; 3],
pub material: u32,
}
#[derive(Clone, Copy, Debug)]
struct BlockIds {
player: &'static str,
position: &'static str,
min: &'static str,
max: &'static str,
material: &'static str,
}
#[derive(Clone, Copy, Debug)]
struct BlockFields {
player: Field<Entity>,
position: [Field<f32>; 3],
min: [Field<f32>; 3],
max: [Field<f32>; 3],
material: Field<u32>,
}
impl BlockFields {
fn resolve(load: &Load<'_>, ty: EventType, ids: BlockIds) -> Result<Self, Off> {
let vector = |id: &str| -> Result<[Field<f32>; 3], Off> {
let at: Field<f32> = ty.field_by_id(load, id)?;
let next = |step: u64| {
let offset = u64::from(at.offset()) + step;
ty.field_at::<f32>(offset)
.ok_or_else(|| Off::new(format!("{id}: past the end of {}", ty.name())))
};
Ok([at, next(4)?, next(8)?])
};
Ok(Self {
player: ty.field_by_id(load, ids.player)?,
position: vector(ids.position)?,
min: vector(ids.min)?,
max: vector(ids.max)?,
material: ty.field_by_id(load, ids.material)?,
})
}
fn read(&self, e: &RawGameEvent<'_>) -> Option<Block> {
let vector = |fields: [Field<f32>; 3]| -> Option<[f32; 3]> {
Some([e.get(fields[0])?, e.get(fields[1])?, e.get(fields[2])?])
};
Some(Block {
player: e.get(self.player)?,
position: vector(self.position)?,
volume_min: vector(self.min)?,
volume_max: vector(self.max)?,
material: e.get(self.material)?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Built {
pub block: Block,
pub item: u32,
}
impl sealed::Sealed for Built {}
impl Kind for Built {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::BUILT_EVENT)?;
let block = BlockFields::resolve(
load,
ty,
BlockIds {
player: ids::BUILT_PLAYER,
position: ids::BUILT_POSITION,
min: ids::BUILT_VOLUME_MIN,
max: ids::BUILT_VOLUME_MAX,
material: ids::BUILT_MATERIAL,
},
)?;
let item: Field<u32> = ty.field_by_id(load, ids::BUILT_ITEM)?;
on(
load,
ty,
move |e| {
Some(Self {
block: block.read(e)?,
item: e.get(item)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct TornDown {
pub block: Block,
}
impl sealed::Sealed for TornDown {}
impl Kind for TornDown {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::TORN_DOWN_EVENT)?;
let block = BlockFields::resolve(
load,
ty,
BlockIds {
player: ids::TORN_DOWN_PLAYER,
position: ids::TORN_DOWN_POSITION,
min: ids::TORN_DOWN_VOLUME_MIN,
max: ids::TORN_DOWN_VOLUME_MAX,
material: ids::TORN_DOWN_MATERIAL,
},
)?;
on(
load,
ty,
move |e| {
Some(Self {
block: block.read(e)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct PickedUp {
pub block: Block,
pub entity: Option<Entity>,
}
impl sealed::Sealed for PickedUp {}
impl Kind for PickedUp {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::PICKED_UP_EVENT)?;
let block = BlockFields::resolve(
load,
ty,
BlockIds {
player: ids::PICKED_UP_PLAYER,
position: ids::PICKED_UP_POSITION,
min: ids::PICKED_UP_VOLUME_MIN,
max: ids::PICKED_UP_VOLUME_MAX,
material: ids::PICKED_UP_MATERIAL,
},
)?;
let picked: Field<Entity> = ty.field_by_id(load, ids::PICKED_UP_ENTITY)?;
on(
load,
ty,
move |e| {
Some(Self {
block: block.read(e)?,
entity: some(e.get(picked)?),
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AltarAction {
Abandon,
Reclaim,
RemoveWater,
SetSpawn,
Other(u8),
}
impl AltarAction {
#[must_use]
pub const fn from_raw(value: u8) -> Self {
match value {
0 => Self::Abandon,
1 => Self::Reclaim,
2 => Self::RemoveWater,
3 => Self::SetSpawn,
other => Self::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct AltarUsed {
pub player: Entity,
pub altar: Entity,
pub action: AltarAction,
}
impl sealed::Sealed for AltarUsed {}
impl Kind for AltarUsed {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::ALTAR_USED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::ALTAR_USED_PLAYER)?;
let altar: Field<Entity> = ty.field_by_id(load, ids::ALTAR_USED_ALTAR)?;
let kind: Field<u8> = ty.field_by_id(load, ids::ALTAR_USED_ACTION)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
altar: e.get(altar)?,
action: AltarAction::from_raw(e.get(kind)?),
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct LootOpened {
pub player: Entity,
pub container: Entity,
pub take_all: bool,
}
impl sealed::Sealed for LootOpened {}
impl Kind for LootOpened {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::LOOT_OPENED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::LOOT_OPENED_PLAYER)?;
let container: Field<Entity> = ty.field_by_id(load, ids::LOOT_OPENED_CONTAINER)?;
let all: Field<bool> = ty.field_by_id(load, ids::LOOT_OPENED_TAKE_ALL)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
container: e.get(container)?,
take_all: e.get(all)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct EquipmentChanged {
pub player: Entity,
pub slot: u8,
}
impl sealed::Sealed for EquipmentChanged {}
impl Kind for EquipmentChanged {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::EQUIPMENT_CHANGED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::EQUIPMENT_CHANGED_PLAYER)?;
let slot: Field<u8> = ty.field_by_id(load, ids::EQUIPMENT_CHANGED_SLOT)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
slot: e.get(slot)?,
})
},
handler,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Revived {
pub player: Entity,
pub by: Option<Entity>,
}
impl sealed::Sealed for Revived {}
impl Kind for Revived {
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(ids::REVIVED_EVENT)?;
let player: Field<Entity> = ty.field_by_id(load, ids::REVIVED_PLAYER)?;
let by: Field<Entity> = ty.field_by_id(load, ids::REVIVED_BY)?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
by: some(e.get(by)?),
})
},
handler,
)
}
}