use crate::ecs::Entity;
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 {
const TYPE_NAME: &'static str;
#[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 {
const TYPE_NAME: &'static str = "keen::ecs::HitEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
const BASE: &str = "keen::ecs::BaseHitEvent";
let ty = load.event_type(Self::TYPE_NAME)?;
let attacker: Field<Entity> = ty.inherited_field(load, BASE, "rootSourceId")?;
let target: Field<Entity> = ty.inherited_field(load, BASE, "targetId")?;
let source: Field<Entity> = ty.field(load, "sourceId")?;
let root_target: Field<Entity> = ty.field(load, "rootTargetId")?;
let weapon: Field<Entity> = ty.field(load, "weaponPideId")?;
let health: Field<i32> = ty.field(load, "healthChange")?;
let stamina: Field<i32> = ty.field(load, "staminaChange")?;
let damage: Field<u16> = ty.field(load, "damageTypes")?;
let attack: Field<u8> = ty.field(load, "attackCategory")?;
let faction: Field<u8> = ty.field(load, "targetFaction")?;
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 = |ty: &str, field: &str| {
load.field_offset(ty, field).and_then(|o| {
usize::try_from(o).map_err(|_| Off::new(format!("{ty}.{field}: out of range")))
})
};
let history = "keen::ecs::HitHistory";
let source = "keen::ecs::ExperienceSource";
let size = usize::try_from(load.type_size(history)?).unwrap_or(0);
let source_size = usize::try_from(load.type_size(source)?).unwrap_or(0);
let entries = offset(history, "history")?;
let entry_size = usize::try_from(load.type_size(Hit::TYPE_NAME)?)
.unwrap_or(0)
.max(1);
let slots = offset(history, "targetTransform")?.saturating_sub(entries) / entry_size;
Ok(Self {
history: world.component_type(load, history)?,
last_index: offset(history, "lastIndex")?,
entries,
slots,
entry_size,
entry_attacker: offset("keen::ecs::BaseHitEvent", "rootSourceId")?,
experience: world.component_type(load, source)?,
last_player: offset(source, "lastHitPlayerId")?,
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 {
const TYPE_NAME: &'static str = "keen::ecs::EntityDiedEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let dead: Field<Entity> = ty.field(load, "deadEntityId")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::LevelUpEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "playerEntityId")?;
let level: Field<i32> = ty.field(load, "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 {
const TYPE_NAME: &'static str = "keen::ecs::XPGainedEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "playerEntityId")?;
let amount: Field<u32> = ty.field(load, "amount")?;
let source: Field<u8> = ty.field(load, "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 {
const TYPE_NAME: &'static str = "keen::ecs::CraftRecipeEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "craftingOperatorId")?;
let station: Field<Entity> = ty.field(load, "craftingStationId")?;
let recipe: Field<u32> = ty.field(load, "recipeId")?;
let amount: Field<u16> = ty.field(load, "recipeAmount")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::CraftRecipeSuccessEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let workshop: Field<u32> = ty.field(load, "workshopId")?;
let recipe: Field<u32> = ty.field(load, "recipeId")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::AcceptOfferedInteractionEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "guestEntityId")?;
let target: Field<Entity> = ty.field(load, "hostEntityId")?;
let offer: Field<u32> = ty.field(load, "offerId")?;
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 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) -> Result<Self, Off> {
let vector = |name: &str| -> Result<[Field<f32>; 3], Off> {
let at: Field<f32> = ty.field(load, name)?;
let next = |step: u64| {
let offset = u64::from(at.offset()) + step;
ty.field_at::<f32>(offset)
.ok_or_else(|| Off::new(format!("{}.{name}: past the end", ty.name())))
};
Ok([at, next(4)?, next(8)?])
};
Ok(Self {
player: ty.field(load, "ownerId")?,
position: vector("position")?,
min: vector("volumeMin")?,
max: vector("volumeMax")?,
material: ty.field(load, "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 {
const TYPE_NAME: &'static str = "keen::ecs::BuildingPlaceEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let block = BlockFields::resolve(load, ty)?;
let item: Field<u32> = ty.field(load, "trackingItemId")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::BuildingTearDownEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let block = BlockFields::resolve(load, ty)?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::BuildingPickUpEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let block = BlockFields::resolve(load, ty)?;
let picked: Field<Entity> = ty.field(load, "pickedEntityId")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::FlameAltarEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "playerEntityId")?;
let altar: Field<Entity> = ty.field(load, "baseEntityId")?;
let kind: Field<u8> = ty.field(load, "type")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::LootInteractionEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "guestId")?;
let container: Field<Entity> = ty.field(load, "hostId")?;
let all: Field<bool> = ty.field(load, "lootAll")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::HandleEquipmentSlotChangedEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "playerEntityId")?;
let slot: Field<u8> = ty.field(load, "equipmentSlot")?;
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 {
const TYPE_NAME: &'static str = "keen::ecs::RevivedPlayerEvent";
fn subscribe<F>(load: &mut Load<'_>, handler: F) -> Result<(), Off>
where
F: Fn(&Self) + Send + Sync + 'static,
{
let ty = load.event_type(Self::TYPE_NAME)?;
let player: Field<Entity> = ty.field(load, "targetEntityId")?;
let by: Field<Entity> = ty.field(load, "sourceEntityId")?;
on(
load,
ty,
move |e| {
Some(Self {
player: e.get(player)?,
by: some(e.get(by)?),
})
},
handler,
)
}
}