diff --git a/client/src/lib.rs b/client/src/lib.rs index 6f7f41731e..112a04c2db 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -2140,12 +2140,7 @@ impl Client { &self.connected_server_constants, |_, _| {}, ); - // TODO: avoid emitting these in the first place - let _ = self - .state - .ecs() - .fetch::>() - .recv_all(); + // TODO: avoid emitting these in the first place OR actually use outcomes // generated locally on the client (if they can be deduplicated from // ones that the server generates or if the client can reliably generate diff --git a/common/Cargo.toml b/common/Cargo.toml index 10fdb3830a..607103a468 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -75,7 +75,7 @@ slotmap = { version = "1.0", features = ["serde"] } indexmap = { version = "1.9.3", features = ["rayon"] } # ECS -specs = { workspace = true, features = ["serde", "storage-event-control"] } +specs = { workspace = true, features = ["serde", "storage-event-control", "shred-derive"] } [dev-dependencies] #bench diff --git a/common/src/combat.rs b/common/src/combat.rs index 2b77be0e8c..d81e331dec 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -14,7 +14,10 @@ use crate::{ Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, HealthChange, Inventory, Ori, Player, Poise, PoiseChange, SkillSet, Stats, }, - event::ServerEvent, + event::{ + BuffEvent, ComboChangeEvent, EmitExt, EnergyChangeEvent, EntityAttackedHookEvent, + HealthChangeEvent, KnockbackEvent, ParryHookEvent, PoiseChangeEvent, + }, outcome::Outcome, resources::{Secs, Time}, states::utils::StageSection, @@ -144,7 +147,7 @@ impl Attack { dir: Dir, damage: Damage, msm: &MaterialStatManifest, - mut emit: impl FnMut(ServerEvent), + emitters: &mut impl EmitExt, mut emit_outcome: impl FnMut(Outcome), ) -> f32 { if damage.value > 0.0 { @@ -164,7 +167,7 @@ impl Attack { pos: target.pos, uid: target.uid, }); - emit(ServerEvent::ParryHook { + emitters.emit(ParryHookEvent { defender: target.entity, attacker: attacker.map(|a| a.entity), source, @@ -203,7 +206,16 @@ impl Attack { strength_modifier: f32, attack_source: AttackSource, time: Time, - mut emit: impl FnMut(ServerEvent), + emitters: &mut ( + impl EmitExt + + EmitExt + + EmitExt + + EmitExt + + EmitExt + + EmitExt + + EmitExt + + EmitExt + ), mut emit_outcome: impl FnMut(Outcome), rng: &mut rand::rngs::ThreadRng, damage_instance_offset: u64, @@ -255,7 +267,7 @@ impl Attack { dir, damage.damage, msm, - &mut emit, + emitters, &mut emit_outcome, ); let change = damage.damage.calculate_health_change( @@ -271,7 +283,7 @@ impl Attack { accumulated_damage += applied_damage; if change.amount.abs() > Health::HEALTH_EPSILON { - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -293,12 +305,12 @@ impl Attack { precise: precision_mult.is_some(), instance: damage_instance, }; - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change: health_change, }); } - emit(ServerEvent::EnergyChange { + emitters.emit(EnergyChangeEvent { entity: target.entity, change: -energy_change, }); @@ -344,12 +356,12 @@ impl Attack { precise: precision_mult.is_some(), time, }; - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change: health_change, }); } else { - emit(ServerEvent::PoiseChange { + emitters.emit(PoiseChangeEvent { entity: target.entity, change: poise_change, }); @@ -366,7 +378,7 @@ impl Attack { let impulse = kb.calculate_impulse(dir, target.char_state) * strength_modifier; if !impulse.is_approx_zero() { - emit(ServerEvent::Knockback { + emitters.emit(KnockbackEvent { entity: target.entity, impulse, }); @@ -374,7 +386,7 @@ impl Attack { }, CombatEffect::EnergyReward(ec) => { if let Some(attacker) = attacker { - emit(ServerEvent::EnergyChange { + emitters.emit(EnergyChangeEvent { entity: attacker.entity, change: *ec * compute_energy_reward_mod(attacker.inventory, msm) @@ -385,7 +397,7 @@ impl Attack { }, CombatEffect::Buff(b) => { if rng.gen::() < b.chance { - emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity: target.entity, buff_change: BuffChange::Add(b.to_buff( time, @@ -409,7 +421,7 @@ impl Attack { instance: rand::random(), }; if change.amount.abs() > Health::HEALTH_EPSILON { - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: attacker_entity, change, }); @@ -435,7 +447,7 @@ impl Attack { cause: Some(damage.damage.source), time, }; - emit(ServerEvent::PoiseChange { + emitters.emit(PoiseChangeEvent { entity: target.entity, change: poise_change, }); @@ -451,7 +463,7 @@ impl Attack { instance: rand::random(), }; if change.amount.abs() > Health::HEALTH_EPSILON { - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -460,7 +472,7 @@ impl Attack { CombatEffect::Combo(c) => { // Not affected by strength modifier as integer if let Some(attacker_entity) = attacker.map(|a| a.entity) { - emit(ServerEvent::ComboChange { + emitters.emit(ComboChangeEvent { entity: attacker_entity, change: *c, }); @@ -476,7 +488,7 @@ impl Attack { change.amount *= damage; change }; - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -484,7 +496,7 @@ impl Attack { }, CombatEffect::RefreshBuff(chance, b) => { if rng.gen::() < *chance { - emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity: target.entity, buff_change: BuffChange::Refresh(*b), }); @@ -497,7 +509,7 @@ impl Attack { change.amount *= damage; change }; - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -510,7 +522,7 @@ impl Attack { change.amount *= damage; change }; - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -543,7 +555,7 @@ impl Attack { { let sufficient_energy = e.current() >= *r; if sufficient_energy { - emit(ServerEvent::EnergyChange { + emitters.emit(EnergyChangeEvent { entity, change: -*r, }); @@ -563,7 +575,7 @@ impl Attack { { let sufficient_combo = c.counter() >= *r; if sufficient_combo { - emit(ServerEvent::ComboChange { + emitters.emit(ComboChangeEvent { entity, change: -(*r as i32), }); @@ -585,7 +597,7 @@ impl Attack { let impulse = kb.calculate_impulse(dir, target.char_state) * strength_modifier; if !impulse.is_approx_zero() { - emit(ServerEvent::Knockback { + emitters.emit(KnockbackEvent { entity: target.entity, impulse, }); @@ -593,7 +605,7 @@ impl Attack { }, CombatEffect::EnergyReward(ec) => { if let Some(attacker) = attacker { - emit(ServerEvent::EnergyChange { + emitters.emit(EnergyChangeEvent { entity: attacker.entity, change: ec * compute_energy_reward_mod(attacker.inventory, msm) @@ -604,7 +616,7 @@ impl Attack { }, CombatEffect::Buff(b) => { if rng.gen::() < b.chance { - emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity: target.entity, buff_change: BuffChange::Add(b.to_buff( time, @@ -628,7 +640,7 @@ impl Attack { instance: rand::random(), }; if change.amount.abs() > Health::HEALTH_EPSILON { - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: attacker_entity, change, }); @@ -654,7 +666,7 @@ impl Attack { cause: Some(attack_source.into()), time, }; - emit(ServerEvent::PoiseChange { + emitters.emit(PoiseChangeEvent { entity: target.entity, change: poise_change, }); @@ -670,7 +682,7 @@ impl Attack { instance: rand::random(), }; if change.amount.abs() > Health::HEALTH_EPSILON { - emit(ServerEvent::HealthChange { + emitters.emit(HealthChangeEvent { entity: target.entity, change, }); @@ -679,7 +691,7 @@ impl Attack { CombatEffect::Combo(c) => { // Not affected by strength modifier as integer if let Some(attacker_entity) = attacker.map(|a| a.entity) { - emit(ServerEvent::ComboChange { + emitters.emit(ComboChangeEvent { entity: attacker_entity, change: c, }); @@ -689,7 +701,7 @@ impl Attack { CombatEffect::StageVulnerable(_, _) => {}, CombatEffect::RefreshBuff(chance, b) => { if rng.gen::() < chance { - emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity: target.entity, buff_change: BuffChange::Refresh(b), }); @@ -704,7 +716,7 @@ impl Attack { // Emits event to handle things that should happen for any successful attack, // regardless of if the attack had any damages or effects in it if is_applied { - emit(ServerEvent::EntityAttackedHook { + emitters.emit(EntityAttackedHookEvent { entity: target.entity, attacker: attacker.map(|a| a.entity), }); diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 2e2d74b2b5..ee7fc1092c 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -4,7 +4,8 @@ use crate::{ ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy, InputAttr, InputKind, Ori, Pos, Vel, }, - event::{LocalEvent, ServerEvent}, + event::{self, EmitExt, LocalEvent}, + event_emitters, resources::Time, states::{ self, @@ -34,19 +35,46 @@ pub struct StateUpdate { pub character_activity: CharacterActivity, } -pub struct OutputEvents<'a> { - local: &'a mut Vec, - server: &'a mut Vec, +event_emitters! { + pub struct CharacterStateEvents[CharacterStateEventEmitters] { + combo: event::ComboChangeEvent, + event: event::AuraEvent, + shoot: event::ShootEvent, + teleport_to: event::TeleportToEvent, + shockwave: event::ShockwaveEvent, + explosion: event::ExplosionEvent, + buff: event::BuffEvent, + inventory_manip: event::InventoryManipEvent, + sprite_summon: event::CreateSpriteEvent, + change_stance: event::ChangeStanceEvent, + create_npc: event::CreateNpcEvent, + energy_change: event::EnergyChangeEvent, + knockback: event::KnockbackEvent, + sprite_light: event::ToggleSpriteLightEvent, + } } -impl<'a> OutputEvents<'a> { - pub fn new(local: &'a mut Vec, server: &'a mut Vec) -> Self { +pub struct OutputEvents<'a, 'b> { + local: &'a mut Vec, + server: &'a mut CharacterStateEventEmitters<'b>, +} + +impl<'a, 'b: 'a> OutputEvents<'a, 'b> { + pub fn new( + local: &'a mut Vec, + server: &'a mut CharacterStateEventEmitters<'b>, + ) -> Self { Self { local, server } } pub fn emit_local(&mut self, event: LocalEvent) { self.local.push(event); } - pub fn emit_server(&mut self, event: ServerEvent) { self.server.push(event); } + pub fn emit_server(&mut self, event: E) + where + CharacterStateEventEmitters<'b>: EmitExt, + { + self.server.emit(event); + } } impl From<&JoinData<'_>> for StateUpdate { diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 85aaca755b..22783a3afb 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -217,6 +217,13 @@ impl GenericChatMsg { Self { chat_type, content } } + pub fn death(kill_source: KillSource, victim: Uid) -> Self { + Self { + chat_type: ChatType::Kill(kill_source, victim), + content: Content::Plain(String::new()), + } + } + pub fn map_group(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg { let chat_type = match self.chat_type { ChatType::Online(a) => ChatType::Online(a), diff --git a/common/src/comp/group.rs b/common/src/comp/group.rs index 390cc4e7a0..99b105aa28 100644 --- a/common/src/comp/group.rs +++ b/common/src/comp/group.rs @@ -2,7 +2,7 @@ use crate::{comp::Alignment, uid::Uid}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use slab::Slab; -use specs::{Component, DerefFlaggedStorage, Join, LendJoin}; +use specs::{storage::GenericReadStorage, Component, DerefFlaggedStorage, Join, LendJoin}; use tracing::{error, warn}; // Primitive group system @@ -82,7 +82,6 @@ impl ChangeNotification { } type GroupsMut<'a> = specs::WriteStorage<'a, Group>; -type Groups<'a> = specs::ReadStorage<'a, Group>; type Alignments<'a> = specs::ReadStorage<'a, Alignment>; type Uids<'a> = specs::ReadStorage<'a, Uid>; @@ -98,7 +97,7 @@ fn pets( entity: specs::Entity, uid: Uid, alignments: &Alignments, - entities: &specs::Entities, + entities: &specs::world::EntitiesRes, ) -> Vec { (entities, alignments) .join() @@ -112,7 +111,7 @@ fn pets( pub fn members<'a>( group: Group, groups: impl Join + 'a, - entities: &'a specs::Entities, + entities: &'a specs::world::EntitiesRes, alignments: &'a Alignments, uids: &'a Uids, ) -> impl Iterator + 'a { @@ -331,7 +330,7 @@ impl GroupManager { groups: &mut GroupsMut, alignments: &Alignments, uids: &Uids, - entities: &specs::Entities, + entities: &specs::world::EntitiesRes, notifier: &mut impl FnMut(specs::Entity, ChangeNotification), ) { self.remove_from_group(member, groups, alignments, uids, entities, notifier, true); @@ -346,7 +345,7 @@ impl GroupManager { groups: &mut GroupsMut, alignments: &Alignments, uids: &Uids, - entities: &specs::Entities, + entities: &specs::world::EntitiesRes, notifier: &mut impl FnMut(specs::Entity, ChangeNotification), to_be_deleted: bool, ) { @@ -493,13 +492,13 @@ impl GroupManager { // Assign new group leader // Does nothing if new leader is not part of a group - pub fn assign_leader( + pub fn assign_leader<'a>( &mut self, new_leader: specs::Entity, - groups: &Groups, - entities: &specs::Entities, - alignments: &Alignments, - uids: &Uids, + groups: impl GenericReadStorage + Join + 'a, + entities: &'a specs::Entities, + alignments: &'a Alignments, + uids: &'a Uids, mut notifier: impl FnMut(specs::Entity, ChangeNotification), ) { let group = match groups.get(new_leader) { diff --git a/common/src/event.rs b/common/src/event.rs index 01d02d298c..ac897d885f 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -7,7 +7,7 @@ use crate::{ dialogue::Subject, invite::{InviteKind, InviteResponse}, misc::PortalData, - DisconnectReason, Ori, Pos, + DisconnectReason, LootOwner, Ori, Pos, UnresolvedChatMsg, Vel, }, lottery::LootSpec, mounting::VolumePos, @@ -20,7 +20,7 @@ use crate::{ Explosion, }; use serde::{Deserialize, Serialize}; -use specs::Entity as EcsEntity; +use specs::{Entity as EcsEntity, World}; use std::{collections::VecDeque, ops::DerefMut, sync::Mutex}; use uuid::Uuid; use vek::*; @@ -132,217 +132,281 @@ impl NpcBuilder { } } -#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587 -#[derive(strum::EnumDiscriminants)] -#[strum_discriminants(repr(usize))] -#[strum_discriminants(derive(strum::EnumVariantNames))] -pub enum ServerEvent { - Explosion { - pos: Vec3, - explosion: Explosion, - owner: Option, - }, - Bonk { - pos: Vec3, - owner: Option, - target: Option, - }, - HealthChange { - entity: EcsEntity, - change: comp::HealthChange, - }, - PoiseChange { - entity: EcsEntity, - change: comp::PoiseChange, - }, - Delete(EcsEntity), - Destroy { - entity: EcsEntity, - cause: comp::HealthChange, - }, - InventoryManip(EcsEntity, comp::InventoryManip), - GroupManip(EcsEntity, comp::GroupManip), - Respawn(EcsEntity), - Shoot { - entity: EcsEntity, - pos: Pos, - dir: Dir, - body: comp::Body, - light: Option, - projectile: comp::Projectile, - speed: f32, - object: Option, - }, - Shockwave { - properties: comp::shockwave::Properties, - pos: Pos, - ori: Ori, - }, - Knockback { - entity: EcsEntity, - impulse: Vec3, - }, - LandOnGround { - entity: EcsEntity, - vel: Vec3, - surface_normal: Vec3, - }, - EnableLantern(EcsEntity), - DisableLantern(EcsEntity), - NpcInteract(EcsEntity, EcsEntity, Subject), - InviteResponse(EcsEntity, InviteResponse), - InitiateInvite(EcsEntity, Uid, InviteKind), - ProcessTradeAction(EcsEntity, TradeId, TradeAction), - Mount(EcsEntity, EcsEntity), - MountVolume(EcsEntity, VolumePos), - Unmount(EcsEntity), - SetPetStay(EcsEntity, EcsEntity, bool), - Possess(Uid, Uid), - /// Inserts default components for a character when loading into the game - InitCharacterData { - entity: EcsEntity, - character_id: CharacterId, - requested_view_distances: crate::ViewDistances, - }, - InitSpectator(EcsEntity, crate::ViewDistances), - UpdateCharacterData { - entity: EcsEntity, - components: ( - comp::Body, - comp::Stats, - comp::SkillSet, - comp::Inventory, - Option, - Vec<(comp::Pet, comp::Body, comp::Stats)>, - comp::ActiveAbilities, - Option, - ), - metadata: UpdateCharacterMetadata, - }, - ExitIngame { - entity: EcsEntity, - }, - // TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type? - CreateNpc { - pos: Pos, - ori: Ori, - npc: NpcBuilder, - rider: Option, - }, - CreateShip { - pos: Pos, - ori: Ori, - ship: comp::ship::Body, - rtsim_entity: Option, - driver: Option, - }, - CreateWaypoint(Vec3), - CreateTeleporter(Vec3, PortalData), - ClientDisconnect(EcsEntity, DisconnectReason), - ClientDisconnectWithoutPersistence(EcsEntity), - Command(EcsEntity, String, Vec), - /// Send a chat message to the player from an npc or other player - Chat(comp::UnresolvedChatMsg), - Aura { - entity: EcsEntity, - aura_change: comp::AuraChange, - }, - Buff { - entity: EcsEntity, - buff_change: comp::BuffChange, - }, - EnergyChange { - entity: EcsEntity, - change: f32, - }, - ComboChange { - entity: EcsEntity, - change: i32, - }, - ParryHook { - defender: EcsEntity, - attacker: Option, - source: AttackSource, - }, - RequestSiteInfo { - entity: EcsEntity, - id: SiteId, - }, - // Attempt to mine a block, turning it into an item - MineBlock { - entity: EcsEntity, - pos: Vec3, - tool: Option, - }, - TeleportTo { - entity: EcsEntity, - target: Uid, - max_range: Option, - }, - CreateSafezone { - range: Option, - pos: Pos, - }, - Sound { - sound: Sound, - }, - CreateSprite { - pos: Vec3, - sprite: SpriteKind, - del_timeout: Option<(f32, f32)>, - }, - TamePet { - pet_entity: EcsEntity, - owner_entity: EcsEntity, - }, - EntityAttackedHook { - entity: EcsEntity, - attacker: Option, - }, - ChangeAbility { - entity: EcsEntity, - slot: usize, - auxiliary_key: comp::ability::AuxiliaryKey, - new_ability: comp::ability::AuxiliaryAbility, - }, - UpdateMapMarker { - entity: EcsEntity, - update: comp::MapMarkerChange, - }, - MakeAdmin { - entity: EcsEntity, - admin: comp::Admin, - uuid: Uuid, - }, - DeleteCharacter { - entity: EcsEntity, - requesting_player_uuid: String, - character_id: CharacterId, - }, - ChangeStance { - entity: EcsEntity, - stance: comp::Stance, - }, - ChangeBody { - entity: EcsEntity, - new_body: comp::Body, - }, - RemoveLightEmitter { - entity: EcsEntity, - }, - TeleportToPosition { - entity: EcsEntity, - position: Vec3, - }, - StartTeleporting { - entity: EcsEntity, - portal: EcsEntity, - }, - ToggleSpriteLight { - entity: EcsEntity, - pos: Vec3, - enable: bool, - }, +pub struct ClientConnectedEvent { + pub entity: EcsEntity, +} +pub struct ClientDisconnectEvent(pub EcsEntity, pub DisconnectReason); +pub struct ClientDisconnectWithoutPersistenceEvent(pub EcsEntity); + +pub struct ChatEvent(pub UnresolvedChatMsg); +pub struct CommandEvent(pub EcsEntity, pub String, pub Vec); + +// Entity Creation +pub struct CreateWaypointEvent(pub Vec3); +pub struct CreateTeleporterEvent(pub Vec3, pub PortalData); + +pub struct CreateNpcEvent { + pub pos: Pos, + pub ori: Ori, + pub npc: NpcBuilder, + pub rider: Option, +} + +pub struct CreateShipEvent { + pub pos: Pos, + pub ori: Ori, + pub ship: comp::ship::Body, + pub rtsim_entity: Option, + pub driver: Option, +} + +pub struct CreateItemDropEvent { + pub pos: Pos, + pub vel: Vel, + pub ori: Ori, + pub item: comp::Item, + pub loot_owner: Option, +} +pub struct CreateObjectEvent { + pub pos: Pos, + pub vel: Vel, + pub body: comp::object::Body, + pub object: Option, + pub item: Option, + pub light_emitter: Option, + pub stats: Option, +} + +pub struct ExplosionEvent { + pub pos: Vec3, + pub explosion: Explosion, + pub owner: Option, +} + +pub struct BonkEvent { + pub pos: Vec3, + pub owner: Option, + pub target: Option, +} + +pub struct HealthChangeEvent { + pub entity: EcsEntity, + pub change: comp::HealthChange, +} + +pub struct PoiseChangeEvent { + pub entity: EcsEntity, + pub change: comp::PoiseChange, +} + +pub struct DeleteEvent(pub EcsEntity); + +pub struct DestroyEvent { + pub entity: EcsEntity, + pub cause: comp::HealthChange, +} + +pub struct InventoryManipEvent(pub EcsEntity, pub comp::InventoryManip); + +pub struct GroupManipEvent(pub EcsEntity, pub comp::GroupManip); + +pub struct RespawnEvent(pub EcsEntity); + +pub struct ShootEvent { + pub entity: EcsEntity, + pub pos: Pos, + pub dir: Dir, + pub body: comp::Body, + pub light: Option, + pub projectile: comp::Projectile, + pub speed: f32, + pub object: Option, +} + +pub struct ShockwaveEvent { + pub properties: comp::shockwave::Properties, + pub pos: Pos, + pub ori: Ori, +} + +pub struct KnockbackEvent { + pub entity: EcsEntity, + pub impulse: Vec3, +} + +pub struct LandOnGroundEvent { + pub entity: EcsEntity, + pub vel: Vec3, + pub surface_normal: Vec3, +} + +pub struct SetLanternEvent(pub EcsEntity, pub bool); + +pub struct NpcInteractEvent(pub EcsEntity, pub EcsEntity, pub Subject); + +pub struct InviteResponseEvent(pub EcsEntity, pub InviteResponse); + +pub struct InitiateInviteEvent(pub EcsEntity, pub Uid, pub InviteKind); + +pub struct ProcessTradeActionEvent(pub EcsEntity, pub TradeId, pub TradeAction); + +pub struct MountEvent(pub EcsEntity, pub EcsEntity); + +pub struct MountVolumeEvent(pub EcsEntity, pub VolumePos); + +pub struct UnmountEvent(pub EcsEntity); + +pub struct SetPetStayEvent(pub EcsEntity, pub EcsEntity, pub bool); + +pub struct PossessEvent(pub Uid, pub Uid); + +pub struct InitializeCharacterEvent { + pub entity: EcsEntity, + pub character_id: CharacterId, + pub requested_view_distances: crate::ViewDistances, +} + +pub struct InitializeSpectatorEvent(pub EcsEntity, pub crate::ViewDistances); + +pub struct UpdateCharacterDataEvent { + pub entity: EcsEntity, + pub components: ( + comp::Body, + comp::Stats, + comp::SkillSet, + comp::Inventory, + Option, + Vec<(comp::Pet, comp::Body, comp::Stats)>, + comp::ActiveAbilities, + Option, + ), + pub metadata: UpdateCharacterMetadata, +} + +pub struct ExitIngameEvent { + pub entity: EcsEntity, +} + +pub struct AuraEvent { + pub entity: EcsEntity, + pub aura_change: comp::AuraChange, +} + +pub struct BuffEvent { + pub entity: EcsEntity, + pub buff_change: comp::BuffChange, +} + +pub struct EnergyChangeEvent { + pub entity: EcsEntity, + pub change: f32, +} + +pub struct ComboChangeEvent { + pub entity: EcsEntity, + pub change: i32, +} + +pub struct ParryHookEvent { + pub defender: EcsEntity, + pub attacker: Option, + pub source: AttackSource, +} + +pub struct RequestSiteInfoEvent { + pub entity: EcsEntity, + pub id: SiteId, +} + +// Attempt to mine a block, turning it into an item +pub struct MineBlockEvent { + pub entity: EcsEntity, + pub pos: Vec3, + pub tool: Option, +} + +pub struct TeleportToEvent { + pub entity: EcsEntity, + pub target: Uid, + pub max_range: Option, +} + +pub struct CreateSafezoneEvent { + pub range: Option, + pub pos: Pos, +} + +pub struct SoundEvent { + pub sound: Sound, +} + +pub struct CreateSpriteEvent { + pub pos: Vec3, + pub sprite: SpriteKind, + pub del_timeout: Option<(f32, f32)>, +} + +pub struct TamePetEvent { + pub pet_entity: EcsEntity, + pub owner_entity: EcsEntity, +} + +pub struct EntityAttackedHookEvent { + pub entity: EcsEntity, + pub attacker: Option, +} + +pub struct ChangeAbilityEvent { + pub entity: EcsEntity, + pub slot: usize, + pub auxiliary_key: comp::ability::AuxiliaryKey, + pub new_ability: comp::ability::AuxiliaryAbility, +} + +pub struct UpdateMapMarkerEvent { + pub entity: EcsEntity, + pub update: comp::MapMarkerChange, +} + +pub struct MakeAdminEvent { + pub entity: EcsEntity, + pub admin: comp::Admin, + pub uuid: Uuid, +} + +pub struct DeleteCharacterEvent { + pub entity: EcsEntity, + pub requesting_player_uuid: String, + pub character_id: CharacterId, +} + +pub struct ChangeStanceEvent { + pub entity: EcsEntity, + pub stance: comp::Stance, +} + +pub struct ChangeBodyEvent { + pub entity: EcsEntity, + pub new_body: comp::Body, +} + +pub struct RemoveLightEmitterEvent { + pub entity: EcsEntity, +} + +pub struct TeleportToPositionEvent { + pub entity: EcsEntity, + pub position: Vec3, +} + +pub struct StartTeleportingEvent { + pub entity: EcsEntity, + pub portal: EcsEntity, +} +pub struct ToggleSpriteLightEvent { + pub entity: EcsEntity, + pub pos: Vec3, + pub enable: bool, } pub struct EventBus { @@ -370,11 +434,15 @@ impl EventBus { pub fn recv_all(&self) -> impl ExactSizeIterator { std::mem::take(self.queue.lock().unwrap().deref_mut()).into_iter() } + + pub fn recv_all_mut(&mut self) -> impl ExactSizeIterator { + std::mem::take(self.queue.get_mut().unwrap()).into_iter() + } } pub struct Emitter<'a, E> { bus: &'a EventBus, - events: VecDeque, + pub events: VecDeque, } impl<'a, E> Emitter<'a, E> { @@ -395,3 +463,137 @@ impl<'a, E> Drop for Emitter<'a, E> { } } } + +pub trait EmitExt { + fn emit(&mut self, event: E); + fn emit_many(&mut self, events: impl IntoIterator); +} + +pub fn register_event_busses(ecs: &mut World) { + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); +} + +/// Define ecs read data for event busses. And a way to convert them all to +/// emitters. +/// +/// # Example: +/// ``` +/// mod some_mod_is_necessary_for_the_test { +/// use veloren_common::event_emitters; +/// pub struct Foo; +/// pub struct Bar; +/// pub struct Baz; +/// event_emitters!( +/// pub struct ReadEvents[EventEmitters] { +/// foo: Foo, bar: Bar, baz: Baz, +/// } +/// ); +/// } +/// ``` +#[macro_export] +macro_rules! event_emitters { + ($($vis:vis struct $read_data:ident[$emitters:ident] { $($ev_ident:ident: $ty:ty),+ $(,)? })+) => { + mod event_emitters { + use super::*; + use specs::shred; + $( + #[derive(specs::SystemData)] + pub struct $read_data<'a> { + $($ev_ident: Option>>),+ + } + + impl<'a> $read_data<'a> { + #[allow(unused)] + pub fn get_emitters(&self) -> $emitters { + $emitters { + $($ev_ident: self.$ev_ident.as_ref().map(|e| e.emitter())),+ + } + } + } + + pub struct $emitters<'a> { + $($ev_ident: Option<$crate::event::Emitter<'a, $ty>>),+ + } + + impl<'a> $emitters<'a> { + #[allow(unused)] + pub fn append(&mut self, mut other: Self) { + $( + self.$ev_ident.as_mut().zip(other.$ev_ident).map(|(a, mut b)| a.append(&mut b.events)); + )+ + } + } + + $( + impl<'a> $crate::event::EmitExt<$ty> for $emitters<'a> { + fn emit(&mut self, event: $ty) { self.$ev_ident.as_mut().map(|e| e.emit(event)); } + fn emit_many(&mut self, events: impl IntoIterator) { self.$ev_ident.as_mut().map(|e| e.emit_many(events)); } + } + )+ + )+ + } + $( + $vis use event_emitters::{$read_data, $emitters}; + )+ + } +} diff --git a/common/src/states/basic_aura.rs b/common/src/states/basic_aura.rs index 0a07c34b0b..4672897a70 100644 --- a/common/src/states/basic_aura.rs +++ b/common/src/states/basic_aura.rs @@ -5,7 +5,7 @@ use crate::{ character_state::OutputEvents, CharacterState, StateUpdate, }, - event::ServerEvent, + event::{AuraEvent, ComboChangeEvent}, resources::Secs, states::{ behavior::{CharacterBehavior, JoinData}, @@ -95,12 +95,12 @@ impl CharacterBehavior for Data { (self.static_data.combo_at_cast.max(1) as f32).sqrt(); }, } - output_events.emit_server(ServerEvent::ComboChange { + output_events.emit_server(ComboChangeEvent { entity: data.entity, change: -(self.static_data.combo_at_cast as i32), }); } - output_events.emit_server(ServerEvent::Aura { + output_events.emit_server(AuraEvent { entity: data.entity, aura_change: AuraChange::Add(aura), }); diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index a03549285a..f717f752e2 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -5,7 +5,7 @@ use crate::{ object::Body::{GrenadeClay, LaserBeam}, Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate, }, - event::{LocalEvent, ServerEvent}, + event::{LocalEvent, ShootEvent}, outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, @@ -127,7 +127,7 @@ impl CharacterBehavior for Data { })) .unwrap_or(data.inputs.look_dir); // Tells server to create and shoot the projectile - output_events.emit_server(ServerEvent::Shoot { + output_events.emit_server(ShootEvent { entity: data.entity, pos, dir, diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 337013b3dd..3fcaaf2354 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -9,7 +9,7 @@ use crate::{ Body::Object, CharacterState, Projectile, StateUpdate, }, - event::{LocalEvent, NpcBuilder, ServerEvent}, + event::{CreateNpcEvent, LocalEvent, NpcBuilder}, npc::NPC_NAMES, outcome::Outcome, skillset_builder::{self, SkillSetBuilder}, @@ -197,7 +197,7 @@ impl CharacterBehavior for Data { let mut rng = rand::thread_rng(); // Send server event to create npc - output_events.emit_server(ServerEvent::CreateNpc { + output_events.emit_server(CreateNpcEvent { pos: comp::Pos( collision_vector - Vec3::unit_z() * obstacle_z + extra_height, ), diff --git a/common/src/states/blink.rs b/common/src/states/blink.rs index 0a251f59ec..7d8fde5b7a 100644 --- a/common/src/states/blink.rs +++ b/common/src/states/blink.rs @@ -1,6 +1,6 @@ use crate::{ comp::{character_state::OutputEvents, CharacterState, StateUpdate}, - event::ServerEvent, + event::TeleportToEvent, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -52,7 +52,7 @@ impl CharacterBehavior for Data { // provided if let Some(input_attr) = self.static_data.ability_info.input_attr { if let Some(target) = input_attr.target_entity { - output_events.emit_server(ServerEvent::TeleportTo { + output_events.emit_server(TeleportToEvent { entity: data.entity, target, max_range: Some(self.static_data.max_range), diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index 49c81dfa56..7f72ebb29b 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -4,7 +4,7 @@ use crate::{ character_state::OutputEvents, projectile::ProjectileConstructor, Body, CharacterState, LightEmitter, Pos, StateUpdate, }, - event::ServerEvent, + event::ShootEvent, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -123,7 +123,7 @@ impl CharacterBehavior for Data { tool_stats, self.static_data.damage_effect, ); - output_events.emit_server(ServerEvent::Shoot { + output_events.emit_server(ShootEvent { entity: data.entity, pos, dir: data.inputs.look_dir, diff --git a/common/src/states/leap_shockwave.rs b/common/src/states/leap_shockwave.rs index f87d613fa1..cbf39a7da5 100644 --- a/common/src/states/leap_shockwave.rs +++ b/common/src/states/leap_shockwave.rs @@ -9,7 +9,7 @@ use crate::{ shockwave::{self, ShockwaveDodgeable}, CharacterState, StateUpdate, }, - event::{LocalEvent, ServerEvent}, + event::{ExplosionEvent, LocalEvent, ShockwaveEvent}, outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, @@ -182,7 +182,7 @@ impl CharacterBehavior for Data { owner: Some(*data.uid), specifier: self.static_data.specifier, }; - output_events.emit_server(ServerEvent::Shockwave { + output_events.emit_server(ShockwaveEvent { properties, pos: *data.pos, ori: *data.ori, @@ -214,7 +214,7 @@ impl CharacterBehavior for Data { reagent: Some(Reagent::White), min_falloff: 0.5, }; - output_events.emit_server(ServerEvent::Explosion { + output_events.emit_server(ExplosionEvent { pos: data.pos.0, explosion, owner: Some(*data.uid), diff --git a/common/src/states/rapid_melee.rs b/common/src/states/rapid_melee.rs index 46c65adb16..37c28e51f8 100644 --- a/common/src/states/rapid_melee.rs +++ b/common/src/states/rapid_melee.rs @@ -1,7 +1,7 @@ use crate::{ combat, comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate}, - event::ServerEvent, + event::ComboChangeEvent, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -117,7 +117,7 @@ impl CharacterBehavior for Data { // Consume combo if any was required if self.static_data.minimum_combo > 0 { - output_events.emit_server(ServerEvent::ComboChange { + output_events.emit_server(ComboChangeEvent { entity: data.entity, change: -data.combo.map_or(0, |c| c.counter() as i32), }); diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs index 2f00c2be22..cb7fc9ee81 100644 --- a/common/src/states/repeater_ranged.rs +++ b/common/src/states/repeater_ranged.rs @@ -4,7 +4,7 @@ use crate::{ character_state::OutputEvents, Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate, }, - event::ServerEvent, + event::{EnergyChangeEvent, ShootEvent}, states::{ behavior::{CharacterBehavior, JoinData}, utils::{StageSection, *}, @@ -144,7 +144,7 @@ impl CharacterBehavior for Data { tool_stats, self.static_data.damage_effect, ); - output_events.emit_server(ServerEvent::Shoot { + output_events.emit_server(ShootEvent { entity: data.entity, pos, dir: direction, @@ -156,7 +156,7 @@ impl CharacterBehavior for Data { }); // Removes energy from character when arrow is fired - output_events.emit_server(ServerEvent::EnergyChange { + output_events.emit_server(EnergyChangeEvent { entity: data.entity, change: -self.static_data.energy_cost, }); diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index 1741cc19e7..30c00f8f67 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -4,7 +4,7 @@ use crate::{ character_state::{AttackFilters, OutputEvents}, CharacterState, StateUpdate, }, - event::ServerEvent, + event::BuffEvent, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -69,7 +69,7 @@ impl CharacterBehavior for Data { }); } else { // Remove burning effect if active - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::RemoveByKind(BuffKind::Burning), }); diff --git a/common/src/states/self_buff.rs b/common/src/states/self_buff.rs index 9975866130..e034ce0df1 100644 --- a/common/src/states/self_buff.rs +++ b/common/src/states/self_buff.rs @@ -4,7 +4,7 @@ use crate::{ character_state::OutputEvents, CharacterState, StateUpdate, }, - event::{LocalEvent, ServerEvent}, + event::{BuffEvent, ComboChangeEvent, LocalEvent}, outcome::Outcome, resources::Secs, states::{ @@ -79,7 +79,7 @@ impl CharacterBehavior for Data { } else { self.static_data.combo_cost }; - output_events.emit_server(ServerEvent::ComboChange { + output_events.emit_server(ComboChangeEvent { entity: data.entity, change: -(combo_consumption as i32), }); @@ -106,7 +106,7 @@ impl CharacterBehavior for Data { if self.static_data.enforced_limit { buff_cat_ids.push(BuffCategory::SelfBuff); - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::RemoveByCategory { all_required: vec![BuffCategory::SelfBuff], @@ -128,7 +128,7 @@ impl CharacterBehavior for Data { *data.time, Some(data.stats), ); - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::Add(buff), }); diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index 5a2551d7d4..4614ca6ab0 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -8,7 +8,7 @@ use crate::{ shockwave::{self, ShockwaveDodgeable}, CharacterState, StateUpdate, }, - event::{LocalEvent, ServerEvent}, + event::{LocalEvent, ShockwaveEvent}, outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, @@ -124,7 +124,7 @@ impl CharacterBehavior for Data { owner: Some(*data.uid), specifier: self.static_data.specifier, }; - output_events.emit_server(ServerEvent::Shockwave { + output_events.emit_server(ShockwaveEvent { properties, pos: *data.pos, ori: *data.ori, diff --git a/common/src/states/sprite_interact.rs b/common/src/states/sprite_interact.rs index 9acee5caec..1e2ae1a923 100644 --- a/common/src/states/sprite_interact.rs +++ b/common/src/states/sprite_interact.rs @@ -4,7 +4,7 @@ use crate::{ character_state::OutputEvents, controller::InputKind, item::ItemDefinitionIdOwned, slot::InvSlotId, CharacterState, InventoryManip, StateUpdate, }, - event::{LocalEvent, ServerEvent}, + event::{InventoryManipEvent, LocalEvent, ToggleSpriteLightEvent}, outcome::Outcome, states::behavior::{CharacterBehavior, JoinData}, terrain::SpriteKind, @@ -117,17 +117,16 @@ impl CharacterBehavior for Data { sprite_pos: self.static_data.sprite_pos, required_item: inv_slot, }; - match self.static_data.sprite_kind { SpriteInteractKind::ToggleLight(enable) => { - output_events.emit_server(ServerEvent::ToggleSpriteLight { + output_events.emit_server(ToggleSpriteLightEvent { entity: data.entity, pos: self.static_data.sprite_pos, enable, }) }, _ => output_events - .emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)), + .emit_server(InventoryManipEvent(data.entity, inv_manip)), } if matches!(self.static_data.sprite_kind, SpriteInteractKind::Unlock) { diff --git a/common/src/states/sprite_summon.rs b/common/src/states/sprite_summon.rs index b25205a936..68e52ca713 100644 --- a/common/src/states/sprite_summon.rs +++ b/common/src/states/sprite_summon.rs @@ -1,6 +1,6 @@ use crate::{ comp::{character_state::OutputEvents, CharacterState, StateUpdate}, - event::{LocalEvent, ServerEvent}, + event::{CreateSpriteEvent, LocalEvent}, outcome::Outcome, spiral::Spiral2d, states::{ @@ -173,7 +173,7 @@ impl CharacterBehavior for Data { }; for i in 0..layers { // Send server event to create sprite - output_events.emit_server(ServerEvent::CreateSprite { + output_events.emit_server(CreateSpriteEvent { pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i), sprite: self.static_data.sprite, del_timeout: self.static_data.del_timeout, diff --git a/common/src/states/use_item.rs b/common/src/states/use_item.rs index c50b2a9f8a..b7fcf9b23a 100644 --- a/common/src/states/use_item.rs +++ b/common/src/states/use_item.rs @@ -10,7 +10,7 @@ use crate::{ }, CharacterState, InventoryManip, StateUpdate, }, - event::ServerEvent, + event::{BuffEvent, InventoryManipEvent}, states::behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; @@ -141,11 +141,11 @@ impl CharacterBehavior for Data { if matches!(update.character, CharacterState::Roll(_)) { // Remove potion/saturation effect if left the use item state early by rolling - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::RemoveByKind(BuffKind::Potion), }); - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::RemoveByKind(BuffKind::Saturation), }); @@ -217,6 +217,6 @@ fn use_item(data: &JoinData, output_events: &mut OutputEvents, state: &Data) { if item_is_same { // Create inventory manipulation event let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot)); - output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + output_events.emit_server(InventoryManipEvent(data.entity, inv_manip)); } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 06a376e506..8f04403e55 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -19,7 +19,7 @@ use crate::{ StateUpdate, }, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, - event::{LocalEvent, ServerEvent}, + event::{BuffEvent, ChangeStanceEvent, ComboChangeEvent, InventoryManipEvent, LocalEvent}, mounting::Volume, outcome::Outcome, states::{behavior::JoinData, utils::CharacterState::Idle, *}, @@ -1058,7 +1058,7 @@ pub fn handle_manipulate_loadout( } else { // Else emit inventory action instantaneously let inv_manip = InventoryManip::Use(slot); - output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + output_events.emit_server(InventoryManipEvent(data.entity, inv_manip)); } }, InventoryAction::Collect(sprite_pos) => { @@ -1137,21 +1137,18 @@ pub fn handle_manipulate_loadout( // For inventory actions without a dedicated character state, just do action instantaneously InventoryAction::Swap(equip, slot) => { let inv_manip = InventoryManip::Swap(Slot::Equip(equip), slot); - output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + output_events.emit_server(InventoryManipEvent(data.entity, inv_manip)); }, InventoryAction::Drop(equip) => { let inv_manip = InventoryManip::Drop(Slot::Equip(equip)); - output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + output_events.emit_server(InventoryManipEvent(data.entity, inv_manip)); }, InventoryAction::Sort => { - output_events.emit_server(ServerEvent::InventoryManip( - data.entity, - InventoryManip::Sort, - )); + output_events.emit_server(InventoryManipEvent(data.entity, InventoryManip::Sort)); }, InventoryAction::Use(slot @ Slot::Equip(_)) => { let inv_manip = InventoryManip::Use(slot); - output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + output_events.emit_server(InventoryManipEvent(data.entity, inv_manip)); }, InventoryAction::Use(Slot::Overflow(_)) => { // Items in overflow slots cannot be used until moved to a real slot @@ -1279,7 +1276,7 @@ fn handle_ability( if let Some(init_event) = ability.ability_meta().init_event { match init_event { AbilityInitEvent::EnterStance(stance) => { - output_events.emit_server(ServerEvent::ChangeStance { + output_events.emit_server(ChangeStanceEvent { entity: data.entity, stance, }); @@ -1614,7 +1611,7 @@ impl HandInfo { pub fn leave_stance(data: &JoinData<'_>, output_events: &mut OutputEvents) { if !matches!(data.stance, Some(Stance::None)) { - output_events.emit_server(ServerEvent::ChangeStance { + output_events.emit_server(ChangeStanceEvent { entity: data.entity, stance: Stance::None, }); @@ -1653,7 +1650,7 @@ impl ComboConsumption { Self::All => combo, Self::Half => (combo + 1) / 2, }; - output_events.emit_server(ServerEvent::ComboChange { + output_events.emit_server(ComboChangeEvent { entity: data.entity, change: -(to_consume as i32), }); @@ -1663,13 +1660,13 @@ impl ComboConsumption { fn loadout_change_hook(data: &JoinData<'_>, output_events: &mut OutputEvents, clear_combo: bool) { if clear_combo { // Reset combo to 0 - output_events.emit_server(ServerEvent::ComboChange { + output_events.emit_server(ComboChangeEvent { entity: data.entity, change: -data.combo.map_or(0, |c| c.counter() as i32), }); } // Clear any buffs from equipped weapons - output_events.emit_server(ServerEvent::Buff { + output_events.emit_server(BuffEvent { entity: data.entity, buff_change: BuffChange::RemoveByCategory { all_required: vec![BuffCategory::RemoveOnLoadoutChange], diff --git a/common/state/src/lib.rs b/common/state/src/lib.rs index aacbfcf589..11f8175a4a 100644 --- a/common/state/src/lib.rs +++ b/common/state/src/lib.rs @@ -8,4 +8,4 @@ mod special_areas; mod state; // TODO: breakup state module and remove glob pub use special_areas::*; -pub use state::{BlockChange, BlockDiff, State, TerrainChanges}; +pub use state::{BlockChange, BlockDiff, ScheduledBlockChange, State, TerrainChanges}; diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 4f380dd0f9..fada52ac8c 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -8,7 +8,7 @@ use common::uid::IdMaps; use common::{ calendar::Calendar, comp, - event::{EventBus, LocalEvent, ServerEvent}, + event::{EventBus, LocalEvent}, link::Is, mounting::{Mount, Rider, VolumeRider, VolumeRiders}, outcome::Outcome, @@ -331,7 +331,6 @@ impl State { ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool)); // TODO: only register on the server - ecs.insert(EventBus::::default()); ecs.insert(comp::group::GroupManager::default()); ecs.insert(SysMetrics::default()); ecs.insert(PhysicsMetrics::default()); @@ -417,6 +416,15 @@ impl State { self.ecs.read_storage().get(entity).copied() } + /// # Panics + /// Panics if `EventBus` is borrowed + pub fn emit_event_now(&self, event: E) + where + EventBus: Resource, + { + self.ecs.write_resource::>().emit_now(event) + } + /// Given mutable access to the resource R, assuming the resource /// component exists (this is already the behavior of functions like `fetch` /// and `write_component_ignore_entity_dead`). Since all of our resources @@ -685,9 +693,7 @@ impl State { self.dispatcher.dispatch(&self.ecs); drop(guard); - section_span!(guard, "maintain ecs"); - self.ecs.maintain(); - drop(guard); + self.maintain_ecs(); if update_terrain { self.apply_terrain_changes_internal(true, block_update); @@ -730,6 +736,11 @@ impl State { drop(guard); } + pub fn maintain_ecs(&mut self) { + span!(_guard, "maintain ecs"); + self.ecs.maintain(); + } + /// Clean up the state after a tick. pub fn cleanup(&mut self) { span!(_guard, "cleanup", "State::cleanup"); diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index 0738d4926a..bda03f6cfc 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -6,19 +6,27 @@ use common::{ group::Group, Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats, }, - event::{Emitter, EventBus, ServerEvent}, + event::{AuraEvent, BuffEvent, EmitExt}, + event_emitters, resources::Time, uid::{IdMaps, Uid}, }; use common_ecs::{Job, Origin, Phase, System}; use specs::{shred, Entities, Entity as EcsEntity, Join, Read, ReadStorage, SystemData}; +event_emitters! { + struct Events[Emitters] { + aura: AuraEvent, + buff: BuffEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, players: ReadStorage<'a, Player>, time: Read<'a, Time>, - server_bus: Read<'a, EventBus>, + events: Events<'a>, id_maps: Read<'a, IdMaps>, cached_spatial_grid: Read<'a, common::CachedSpatialGrid>, positions: ReadStorage<'a, Pos>, @@ -42,7 +50,7 @@ impl<'a> System<'a> for Sys { const PHASE: Phase = Phase::Create; fn run(_job: &mut Job, read_data: Self::SystemData) { - let mut server_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); // Iterate through all entities with an aura for (entity, pos, auras_comp, uid) in ( @@ -118,14 +126,14 @@ impl<'a> System<'a> for Sys { target_buffs, stats, &read_data, - &mut server_emitter, + &mut emitters, ); } } }); } if !expired_auras.is_empty() { - server_emitter.emit(ServerEvent::Aura { + emitters.emit(AuraEvent { entity, aura_change: AuraChange::RemoveByKey(expired_auras), }); @@ -145,7 +153,7 @@ fn activate_aura( target_buffs: &Buffs, stats: Option<&Stats>, read_data: &ReadData, - server_emitter: &mut Emitter, + emitters: &mut impl EmitExt, ) { let should_activate = match aura.aura_kind { AuraKind::Buff { kind, source, .. } => { @@ -223,7 +231,7 @@ fn activate_aura( && buff.data.strength >= data.strength }); if emit_buff { - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity: target, buff_change: BuffChange::Add(Buff::new( kind, diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index d22bae4bc6..c6470e6056 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -5,7 +5,8 @@ use common::{ Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori, Player, Pos, Scale, Stats, }, - event::{EventBus, ServerEvent}, + event::{self, EmitExt, EventBus}, + event_emitters, outcome::Outcome, resources::{DeltaTime, Time}, terrain::TerrainGrid, @@ -21,11 +22,24 @@ use specs::{ }; use vek::*; +event_emitters! { + struct ReadAttackEvents[AttackEmitters] { + health_change: event::HealthChangeEvent, + energy_change: event::EnergyChangeEvent, + poise_change: event::PoiseChangeEvent, + sound: event::SoundEvent, + parry_hook: event::ParryHookEvent, + kockback: event::KnockbackEvent, + entity_attack_hoow: event::EntityAttackedHookEvent, + combo_change: event::ComboChangeEvent, + buff: event::BuffEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, players: ReadStorage<'a, Player>, - server_bus: Read<'a, EventBus>, time: Read<'a, Time>, dt: Read<'a, DeltaTime>, terrain: ReadExpect<'a, TerrainGrid>, @@ -46,6 +60,7 @@ pub struct ReadData<'a> { character_states: ReadStorage<'a, CharacterState>, buffs: ReadStorage<'a, Buffs>, outcomes: Read<'a, EventBus>, + events: ReadAttackEvents<'a>, } /// This system is responsible for handling beams that heal or do damage @@ -59,7 +74,6 @@ impl<'a> System<'a> for Sys { const PHASE: Phase = Phase::Create; fn run(job: &mut Job, (read_data, mut beams): Self::SystemData) { - let mut server_emitter = read_data.server_bus.emitter(); let mut outcomes_emitter = read_data.outcomes.emitter(); ( @@ -100,7 +114,8 @@ impl<'a> System<'a> for Sys { job.cpu_stats.measure(ParMode::Rayon); // Beams - let (server_events, add_hit_entities, new_outcomes) = ( + // Emitters will append their events when dropped. + let (_emitters, add_hit_entities, new_outcomes) = ( &read_data.entities, &read_data.positions, &read_data.orientations, @@ -109,14 +124,14 @@ impl<'a> System<'a> for Sys { ) .par_join() .fold( - || (Vec::new(), Vec::new(), Vec::new()), - |(mut server_events, mut add_hit_entities, mut outcomes), + || (read_data.events.get_emitters(), Vec::new(), Vec::new()), + |(mut emitters, mut add_hit_entities, mut outcomes), (entity, pos, ori, uid, beam)| { // Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing // this becomes a bottleneck we can look into alternatives. let mut rng = rand::thread_rng(); if rng.gen_bool(0.005) { - server_events.push(ServerEvent::Sound { + emitters.emit(event::SoundEvent { sound: Sound::new(SoundKind::Beam, pos.0, 13.0, read_data.time.0), }); } @@ -273,7 +288,7 @@ impl<'a> System<'a> for Sys { 1.0, AttackSource::Beam, *read_data.time, - |e| server_events.push(e), + &mut emitters, |o| outcomes.push(o), &mut rng, 0, @@ -282,14 +297,14 @@ impl<'a> System<'a> for Sys { add_hit_entities.push((entity, target)); } }); - (server_events, add_hit_entities, outcomes) + (emitters, add_hit_entities, outcomes) }, ) .reduce( - || (Vec::new(), Vec::new(), Vec::new()), + || (read_data.events.get_emitters(), Vec::new(), Vec::new()), |(mut events_a, mut hit_entities_a, mut outcomes_a), - (mut events_b, mut hit_entities_b, mut outcomes_b)| { - events_a.append(&mut events_b); + (events_b, mut hit_entities_b, mut outcomes_b)| { + events_a.append(events_b); hit_entities_a.append(&mut hit_entities_b); outcomes_a.append(&mut outcomes_b); (events_a, hit_entities_a, outcomes_a) @@ -298,7 +313,6 @@ impl<'a> System<'a> for Sys { job.cpu_stats.measure(ParMode::Single); outcomes_emitter.emit_many(new_outcomes); - server_emitter.emit_many(server_events); for (entity, hit_entity) in add_hit_entities { if let Some(ref mut beam) = beams.get_mut(entity) { diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index c1bb6e0080..c35f399136 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -13,7 +13,11 @@ use common::{ Energy, Group, Health, HealthChange, Inventory, LightEmitter, ModifierKind, PhysicsState, Pos, Stats, }, - event::{Emitter, EventBus, ServerEvent}, + event::{ + BuffEvent, ChangeBodyEvent, CreateSpriteEvent, EmitExt, EnergyChangeEvent, + HealthChangeEvent, RemoveLightEmitterEvent, SoundEvent, + }, + event_emitters, outcome::Outcome, resources::{DeltaTime, Secs, Time}, terrain::SpriteKind, @@ -29,12 +33,24 @@ use specs::{ }; use vek::Vec3; +event_emitters! { + struct Events[EventEmitters] { + buff: BuffEvent, + change_body: ChangeBodyEvent, + remove_light: RemoveLightEmitterEvent, + health_change: HealthChangeEvent, + energy_change: EnergyChangeEvent, + sound: SoundEvent, + create_sprite: CreateSpriteEvent, + outcome: Outcome, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, dt: Read<'a, DeltaTime>, - server_bus: Read<'a, EventBus>, - outcome_bus: Read<'a, EventBus>, + events: Events<'a>, inventories: ReadStorage<'a, Inventory>, healths: ReadStorage<'a, Health>, energies: ReadStorage<'a, Energy>, @@ -60,8 +76,7 @@ impl<'a> System<'a> for Sys { const PHASE: Phase = Phase::Create; fn run(job: &mut Job, (read_data, mut stats): Self::SystemData) { - let mut server_emitter = read_data.server_bus.emitter(); - let mut outcome = read_data.outcome_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); let dt = read_data.dt.0; // Set to false to avoid spamming server stats.set_event_emission(false); @@ -116,11 +131,11 @@ impl<'a> System<'a> for Sys { // slower than parallel checking above for e in to_put_out_campfires { { - server_emitter.emit(ServerEvent::ChangeBody { + emitters.emit(ChangeBodyEvent { entity: e, new_body: Body::Object(object::Body::Campfire), }); - server_emitter.emit(ServerEvent::RemoveLightEmitter { entity: e }); + emitters.emit(RemoveLightEmitterEvent { entity: e }); } } } @@ -144,7 +159,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::EnsnaringVines) | Some(SpriteKind::EnsnaringWeb) ) { // If on ensnaring vines, apply ensnared debuff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Ensnared, @@ -161,7 +176,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::SeaUrchin) ) { // If touching Sea Urchin apply Bleeding buff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, @@ -181,17 +196,17 @@ impl<'a> System<'a> for Sys { // TODO: Determine a better place to emit sprite change events if let Some(pos) = read_data.positions.get(entity) { // If touching Trap - change sprite and apply Bleeding buff - server_emitter.emit(ServerEvent::CreateSprite { + emitters.emit(CreateSpriteEvent { pos: Vec3::new(pos.0.x as i32, pos.0.y as i32, pos.0.z as i32 - 1), sprite: SpriteKind::HaniwaTrapTriggered, del_timeout: Some((4.0, 1.0)), }); - server_emitter.emit(ServerEvent::Sound { + emitters.emit(SoundEvent { sound: Sound::new(SoundKind::Trap, pos.0, 12.0, read_data.time.0), }); - outcome.emit(Outcome::Slash { pos: pos.0 }); + emitters.emit(Outcome::Slash { pos: pos.0 }); - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, @@ -209,7 +224,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::IronSpike | SpriteKind::HaniwaTrapTriggered) ) { // If touching Iron Spike apply Bleeding buff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, @@ -226,7 +241,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::HotSurface) ) { // If touching a hot surface apply Burning buff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Burning, @@ -243,7 +258,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::IceSpike) ) { // When standing on IceSpike, apply bleeding - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, @@ -255,7 +270,7 @@ impl<'a> System<'a> for Sys { )), }); // When standing on IceSpike also apply Frozen - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Frozen, @@ -272,7 +287,7 @@ impl<'a> System<'a> for Sys { Some(SpriteKind::FireBlock) ) { // If on FireBlock vines, apply burning buff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Burning, @@ -293,7 +308,7 @@ impl<'a> System<'a> for Sys { }) ) { // If in lava fluid, apply burning debuff - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Burning, @@ -313,7 +328,7 @@ impl<'a> System<'a> for Sys { ) && buff_comp.kinds[BuffKind::Burning].is_some() { // If in water fluid and currently burning, remove burning debuffs - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::RemoveByKind(BuffKind::Burning), }); @@ -363,7 +378,7 @@ impl<'a> System<'a> for Sys { }; if replace { expired_buffs.push(buff_key); - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::Add(Buff::new( buff.kind, @@ -458,7 +473,7 @@ impl<'a> System<'a> for Sys { energy, entity, buff_owner, - &mut server_emitter, + &mut emitters, dt, *read_data.time, expired_buffs.contains(&buff_key), @@ -472,12 +487,12 @@ impl<'a> System<'a> for Sys { // Update body if needed. let new_body = body_override.unwrap_or(stat.original_body); if new_body != *body { - server_emitter.emit(ServerEvent::ChangeBody { entity, new_body }); + emitters.emit(ChangeBodyEvent { entity, new_body }); } // Remove buffs that expire if !expired_buffs.is_empty() { - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::RemoveByKey(expired_buffs), }); @@ -485,7 +500,7 @@ impl<'a> System<'a> for Sys { // Remove buffs that don't persist on death if health.is_dead { - server_emitter.emit(ServerEvent::Buff { + emitters.emit(BuffEvent { entity, buff_change: BuffChange::RemoveByCategory { all_required: vec![], @@ -515,7 +530,9 @@ fn execute_effect( energy: &Energy, entity: Entity, buff_owner: Option, - server_emitter: &mut Emitter, + server_emitter: &mut ( + impl EmitExt + EmitExt + EmitExt + ), dt: f32, time: Time, buff_will_expire: bool, @@ -577,7 +594,7 @@ fn execute_effect( DamageContributor::new(uid, read_data.groups.get(entity).cloned()) }) }); - server_emitter.emit(ServerEvent::HealthChange { + server_emitter.emit(HealthChangeEvent { entity, change: HealthChange { amount, @@ -602,7 +619,7 @@ fn execute_effect( ModifierKind::Additive => amount, ModifierKind::Multiplicative => energy.maximum() * amount, }; - server_emitter.emit(ServerEvent::EnergyChange { + server_emitter.emit(EnergyChangeEvent { entity, change: amount, }); @@ -718,7 +735,7 @@ fn execute_effect( }, BuffEffect::BuffImmunity(buff_kind) => { if buffs_comp.contains(*buff_kind) { - server_emitter.emit(ServerEvent::Buff { + server_emitter.emit(BuffEvent { entity, buff_change: BuffChange::RemoveByKind(*buff_kind), }); diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 37b09aca26..3ac499e9fa 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -5,13 +5,13 @@ use specs::{ use common::{ comp::{ self, - character_state::OutputEvents, + character_state::{CharacterStateEvents, OutputEvents}, inventory::item::{tool::AbilityMap, MaterialStatManifest}, ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density, Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, StateUpdate, Stats, Vel, }, - event::{EventBus, LocalEvent, ServerEvent}, + event::{self, EventBus, KnockbackEvent, LocalEvent}, link::Is, mounting::{Rider, VolumeRider}, outcome::Outcome, @@ -28,7 +28,7 @@ use common_ecs::{Job, Origin, Phase, System}; #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, - server_bus: Read<'a, EventBus>, + events: CharacterStateEvents<'a>, local_bus: Read<'a, EventBus>, dt: Read<'a, DeltaTime>, time: Read<'a, Time>, @@ -96,13 +96,12 @@ impl<'a> System<'a> for Sys { outcomes, ): Self::SystemData, ) { - let mut server_emitter = read_data.server_bus.emitter(); let mut local_emitter = read_data.local_bus.emitter(); let mut outcomes_emitter = outcomes.emitter(); + let mut emitters = read_data.events.get_emitters(); let mut local_events = Vec::new(); - let mut server_events = Vec::new(); - let mut output_events = OutputEvents::new(&mut local_events, &mut server_events); + let mut output_events = OutputEvents::new(&mut local_events, &mut emitters); let join = ( &read_data.entities, @@ -179,7 +178,7 @@ impl<'a> System<'a> for Sys { state: poise_state, }); if let Some(impulse_strength) = impulse_strength { - server_emitter.emit(ServerEvent::Knockback { + output_events.emit_server(KnockbackEvent { entity, impulse: impulse_strength * *poise.knockback(), }); @@ -255,7 +254,6 @@ impl<'a> System<'a> for Sys { }); local_emitter.append_vec(local_events); - server_emitter.append_vec(server_events); } } @@ -297,7 +295,7 @@ impl Sys { join.controller.queued_inputs.remove(&input); } if state_update.swap_equipped_weapons { - output_events.emit_server(ServerEvent::InventoryManip( + output_events.emit_server(event::InventoryManipEvent( join.entity, InventoryManip::SwapEquippedWeapons, )); diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index 9b6dec9ab2..e1380a96bc 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -4,7 +4,8 @@ use common::{ agent::{Sound, SoundKind}, Body, BuffChange, Collider, ControlEvent, Controller, Pos, Scale, }, - event::{EventBus, ServerEvent}, + event::{self, EmitExt}, + event_emitters, terrain::TerrainGrid, uid::IdMaps, }; @@ -12,11 +13,33 @@ use common_ecs::{Job, Origin, Phase, System}; use specs::{shred, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, WriteStorage}; use vek::*; +event_emitters! { + struct Events[EventEmitters] { + mount: event::MountEvent, + mount_volume: event::MountVolumeEvent, + set_pet_stay: event::SetPetStayEvent, + unmount: event::UnmountEvent, + lantern: event::SetLanternEvent, + npc_interact: event::NpcInteractEvent, + initiate_invite: event::InitiateInviteEvent, + invite_response: event::InviteResponseEvent, + process_trade_action: event::ProcessTradeActionEvent, + inventory_manip: event::InventoryManipEvent, + group_manip: event::GroupManipEvent, + respawn: event::RespawnEvent, + sound: event::SoundEvent, + change_ability: event::ChangeAbilityEvent, + change_stance: event::ChangeStanceEvent, + start_teleporting: event::StartTeleportingEvent, + buff: event::BuffEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, id_maps: Read<'a, IdMaps>, - server_bus: Read<'a, EventBus>, + events: Events<'a>, terrain_grid: ReadExpect<'a, TerrainGrid>, positions: ReadStorage<'a, Pos>, bodies: ReadStorage<'a, Body>, @@ -35,7 +58,7 @@ impl<'a> System<'a> for Sys { const PHASE: Phase = Phase::Create; fn run(_job: &mut Job, (read_data, mut controllers): Self::SystemData) { - let mut server_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); for (entity, controller) in (&read_data.entities, &mut controllers).join() { // Sanitize inputs to avoid clients sending bad data @@ -46,7 +69,7 @@ impl<'a> System<'a> for Sys { match event { ControlEvent::Mount(mountee_uid) => { if let Some(mountee_entity) = read_data.id_maps.uid_entity(mountee_uid) { - server_emitter.emit(ServerEvent::Mount(entity, mountee_entity)); + emitters.emit(event::MountEvent(entity, mountee_entity)); } }, ControlEvent::MountVolume(volume) => { @@ -56,51 +79,49 @@ impl<'a> System<'a> for Sys { &read_data.colliders, ) { if block.is_mountable() { - server_emitter.emit(ServerEvent::MountVolume(entity, volume)); + emitters.emit(event::MountVolumeEvent(entity, volume)); } } }, ControlEvent::SetPetStay(pet_uid, stay) => { if let Some(pet_entity) = read_data.id_maps.uid_entity(pet_uid) { - server_emitter.emit(ServerEvent::SetPetStay(entity, pet_entity, stay)); + emitters.emit(event::SetPetStayEvent(entity, pet_entity, stay)); } }, ControlEvent::RemoveBuff(buff_id) => { - server_emitter.emit(ServerEvent::Buff { + emitters.emit(event::BuffEvent { entity, buff_change: BuffChange::RemoveFromController(buff_id), }); }, - ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), + ControlEvent::Unmount => emitters.emit(event::UnmountEvent(entity)), ControlEvent::EnableLantern => { - server_emitter.emit(ServerEvent::EnableLantern(entity)) + emitters.emit(event::SetLanternEvent(entity, true)) }, ControlEvent::DisableLantern => { - server_emitter.emit(ServerEvent::DisableLantern(entity)) + emitters.emit(event::SetLanternEvent(entity, false)) }, ControlEvent::Interact(npc_uid, subject) => { if let Some(npc_entity) = read_data.id_maps.uid_entity(npc_uid) { - server_emitter - .emit(ServerEvent::NpcInteract(entity, npc_entity, subject)); + emitters.emit(event::NpcInteractEvent(entity, npc_entity, subject)); } }, ControlEvent::InitiateInvite(inviter_uid, kind) => { - server_emitter.emit(ServerEvent::InitiateInvite(entity, inviter_uid, kind)); + emitters.emit(event::InitiateInviteEvent(entity, inviter_uid, kind)); }, ControlEvent::InviteResponse(response) => { - server_emitter.emit(ServerEvent::InviteResponse(entity, response)); + emitters.emit(event::InviteResponseEvent(entity, response)); }, ControlEvent::PerformTradeAction(trade_id, action) => { - server_emitter - .emit(ServerEvent::ProcessTradeAction(entity, trade_id, action)); + emitters.emit(event::ProcessTradeActionEvent(entity, trade_id, action)); }, ControlEvent::InventoryEvent(event) => { - server_emitter.emit(ServerEvent::InventoryManip(entity, event.into())); + emitters.emit(event::InventoryManipEvent(entity, event.into())); }, ControlEvent::GroupManip(manip) => { - server_emitter.emit(ServerEvent::GroupManip(entity, manip)) + emitters.emit(event::GroupManipEvent(entity, manip)) }, - ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), + ControlEvent::Respawn => emitters.emit(event::RespawnEvent(entity)), ControlEvent::Utterance(kind) => { if let (Some(pos), Some(body), scale) = ( read_data.positions.get(entity), @@ -114,7 +135,7 @@ impl<'a> System<'a> for Sys { 8.0, // TODO: Come up with a better way of determining this 1.0, ); - server_emitter.emit(ServerEvent::Sound { sound }); + emitters.emit(event::SoundEvent { sound }); } }, ControlEvent::ChangeAbility { @@ -122,7 +143,7 @@ impl<'a> System<'a> for Sys { auxiliary_key, new_ability, } => { - server_emitter.emit(ServerEvent::ChangeAbility { + emitters.emit(event::ChangeAbilityEvent { entity, slot, auxiliary_key, @@ -130,14 +151,14 @@ impl<'a> System<'a> for Sys { }); }, ControlEvent::LeaveStance => { - server_emitter.emit(ServerEvent::ChangeStance { + emitters.emit(event::ChangeStanceEvent { entity, stance: Stance::None, }); }, ControlEvent::ActivatePortal(portal_uid) => { if let Some(portal) = read_data.id_maps.uid_entity(portal_uid) { - server_emitter.emit(ServerEvent::StartTeleporting { entity, portal }); + emitters.emit(event::StartTeleportingEvent { entity, portal }); } }, } diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index b51b224dff..a74463d536 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -6,7 +6,8 @@ use common::{ Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos, Scale, Stats, }, - event::{EventBus, ServerEvent}, + event::{self, EmitExt, EventBus}, + event_emitters, outcome::Outcome, resources::Time, terrain::TerrainGrid, @@ -22,6 +23,21 @@ use specs::{ }; use vek::*; +event_emitters! { + struct ReadAttackEvents[AttackEmitters] { + health_change: event::HealthChangeEvent, + energy_change: event::EnergyChangeEvent, + poise_change: event::PoiseChangeEvent, + sound: event::SoundEvent, + mine_block: event::MineBlockEvent, + parry_hook: event::ParryHookEvent, + kockback: event::KnockbackEvent, + entity_attack_hook: event::EntityAttackedHookEvent, + combo_change: event::ComboChangeEvent, + buff: event::BuffEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { time: Read<'a, Time>, @@ -40,10 +56,10 @@ pub struct ReadData<'a> { inventories: ReadStorage<'a, Inventory>, groups: ReadStorage<'a, Group>, char_states: ReadStorage<'a, CharacterState>, - server_bus: Read<'a, EventBus>, stats: ReadStorage<'a, Stats>, combos: ReadStorage<'a, Combo>, buffs: ReadStorage<'a, Buffs>, + events: ReadAttackEvents<'a>, } /// This system is responsible for handling accepted inputs like moving or @@ -63,7 +79,7 @@ impl<'a> System<'a> for Sys { const PHASE: Phase = Phase::Create; fn run(_job: &mut Job, (read_data, mut melee_attacks, outcomes): Self::SystemData) { - let mut server_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); let mut outcomes_emitter = outcomes.emitter(); let mut rng = rand::thread_rng(); @@ -82,7 +98,7 @@ impl<'a> System<'a> for Sys { if melee_attack.applied { continue; } - server_emitter.emit(ServerEvent::Sound { + emitters.emit(event::SoundEvent { sound: Sound::new(SoundKind::Melee, pos.0, 2.0, read_data.time.0), }); melee_attack.applied = true; @@ -103,7 +119,7 @@ impl<'a> System<'a> for Sys { if eye_pos.distance_squared(block_pos.map(|e| e as f32 + 0.5)) < (rad + scale * melee_attack.range).powi(2) { - server_emitter.emit(ServerEvent::MineBlock { + emitters.emit(event::MineBlockEvent { entity: attacker, pos: block_pos, tool, @@ -270,7 +286,7 @@ impl<'a> System<'a> for Sys { strength, AttackSource::Melee, *read_data.time, - |e| server_emitter.emit(e), + &mut emitters, |o| outcomes_emitter.emit(o), &mut rng, offset as u64, diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index e6acbb1d48..89377b3f6a 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -7,7 +7,8 @@ use common::{ PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel, }, consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY}, - event::{EventBus, ServerEvent}, + event::{EmitExt, EventBus, LandOnGroundEvent}, + event_emitters, link::Is, mounting::{Rider, VolumeRider}, outcome::Outcome, @@ -123,6 +124,12 @@ fn calc_z_limit(char_state_maybe: Option<&CharacterState>, collider: &Collider) collider.get_z_limits(modifier) } +event_emitters! { + struct Events[Emitters] { + land_on_ground: LandOnGroundEvent, + } +} + /// This system applies forces and calculates new positions and velocities. #[derive(Default)] pub struct Sys; @@ -130,10 +137,10 @@ pub struct Sys; #[derive(SystemData)] pub struct PhysicsRead<'a> { entities: Entities<'a>, + events: Events<'a>, uids: ReadStorage<'a, Uid>, terrain: ReadExpect<'a, TerrainGrid>, dt: Read<'a, DeltaTime>, - event_bus: Read<'a, EventBus>, game_mode: ReadExpect<'a, GameMode>, scales: ReadStorage<'a, Scale>, stickies: ReadStorage<'a, Sticky>, @@ -1364,16 +1371,16 @@ impl<'a> PhysicsData<'a> { } drop(guard); - let mut event_emitter = read.event_bus.emitter(); - land_on_grounds - .into_iter() - .for_each(|(entity, vel, surface_normal)| { - event_emitter.emit(ServerEvent::LandOnGround { + let mut emitters = read.events.get_emitters(); + emitters.emit_many( + land_on_grounds + .into_iter() + .map(|(entity, vel, surface_normal)| LandOnGroundEvent { entity, vel: vel.0, surface_normal, - }); - }); + }), + ); } fn update_cached_spatial_grid(&mut self) { diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 814bd21f10..9c7d7ccf0c 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -5,7 +5,12 @@ use common::{ projectile, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel, }, - event::{Emitter, EventBus, ServerEvent}, + event::{ + BonkEvent, BuffEvent, ComboChangeEvent, DeleteEvent, EmitExt, Emitter, EnergyChangeEvent, + EntityAttackedHookEvent, EventBus, ExplosionEvent, HealthChangeEvent, KnockbackEvent, + ParryHookEvent, PoiseChangeEvent, PossessEvent, SoundEvent, + }, + event_emitters, outcome::Outcome, resources::{DeltaTime, Time}, uid::{IdMaps, Uid}, @@ -25,6 +30,24 @@ use vek::*; use common::terrain::TerrainGrid; +event_emitters! { + struct Events[Emitters] { + sound: SoundEvent, + delete: DeleteEvent, + explosion: ExplosionEvent, + health_change: HealthChangeEvent, + energy_change: EnergyChangeEvent, + poise_change: PoiseChangeEvent, + parry_hook: ParryHookEvent, + kockback: KnockbackEvent, + entity_attack_hoow: EntityAttackedHookEvent, + combo_change: ComboChangeEvent, + buff: BuffEvent, + bonk: BonkEvent, + possess: PossessEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { time: Read<'a, Time>, @@ -32,7 +55,7 @@ pub struct ReadData<'a> { players: ReadStorage<'a, Player>, dt: Read<'a, DeltaTime>, id_maps: Read<'a, IdMaps>, - server_bus: Read<'a, EventBus>, + events: Events<'a>, uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, alignments: ReadStorage<'a, Alignment>, @@ -69,7 +92,7 @@ impl<'a> System<'a> for Sys { _job: &mut Job, (read_data, mut orientations, mut projectiles, outcomes): Self::SystemData, ) { - let mut server_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); let mut outcomes_emitter = outcomes.emitter(); let mut rng = rand::thread_rng(); @@ -88,7 +111,7 @@ impl<'a> System<'a> for Sys { .and_then(|uid| read_data.id_maps.uid_entity(uid)); if physics.on_surface().is_none() && rng.gen_bool(0.05) { - server_emitter.emit(ServerEvent::Sound { + emitters.emit(SoundEvent { sound: Sound::new(SoundKind::Projectile, pos.0, 4.0, read_data.time.0), }); } @@ -174,7 +197,7 @@ impl<'a> System<'a> for Sys { &read_data, &mut projectile_vanished, &mut outcomes_emitter, - &mut server_emitter, + &mut emitters, &mut rng, ); } @@ -200,18 +223,18 @@ impl<'a> System<'a> for Sys { .get(entity) .map_or_else(Vec3::zero, |ori| ori.look_vec()); let offset = -0.2 * projectile_direction; - server_emitter.emit(ServerEvent::Explosion { + emitters.emit(ExplosionEvent { pos: pos.0 + offset, explosion: e, owner: projectile.owner, }); }, projectile::Effect::Vanish => { - server_emitter.emit(ServerEvent::Delete(entity)); + emitters.emit(DeleteEvent(entity)); projectile_vanished = true; }, projectile::Effect::Bonk => { - server_emitter.emit(ServerEvent::Bonk { + emitters.emit(BonkEvent { pos: pos.0, owner: projectile.owner, target: None, @@ -231,7 +254,7 @@ impl<'a> System<'a> for Sys { } if projectile.time_left == Duration::default() { - server_emitter.emit(ServerEvent::Delete(entity)); + emitters.emit(DeleteEvent(entity)); } projectile.time_left = projectile .time_left @@ -264,7 +287,7 @@ fn dispatch_hit( read_data: &ReadData, projectile_vanished: &mut bool, outcomes_emitter: &mut Emitter, - server_emitter: &mut Emitter, + emitters: &mut Emitters, rng: &mut rand::rngs::ThreadRng, ) { match projectile_info.effect { @@ -437,7 +460,7 @@ fn dispatch_hit( 1.0, AttackSource::Projectile, *read_data.time, - |e| server_emitter.emit(e), + emitters, |o| outcomes_emitter.emit(o), rng, 0, @@ -446,7 +469,7 @@ fn dispatch_hit( projectile::Effect::Explode(e) => { let Pos(pos) = *projectile_info.pos; let owner_uid = projectile_info.owner_uid; - server_emitter.emit(ServerEvent::Explosion { + emitters.emit(ExplosionEvent { pos, explosion: e, owner: owner_uid, @@ -455,7 +478,7 @@ fn dispatch_hit( projectile::Effect::Bonk => { let Pos(pos) = *projectile_info.pos; let owner_uid = projectile_info.owner_uid; - server_emitter.emit(ServerEvent::Bonk { + emitters.emit(BonkEvent { pos, owner: owner_uid, target: Some(projectile_target_info.uid), @@ -463,7 +486,7 @@ fn dispatch_hit( }, projectile::Effect::Vanish => { let entity = projectile_info.entity; - server_emitter.emit(ServerEvent::Delete(entity)); + emitters.emit(DeleteEvent(entity)); *projectile_vanished = true; }, projectile::Effect::Possess => { @@ -471,7 +494,7 @@ fn dispatch_hit( let owner_uid = projectile_info.owner_uid; if let Some(owner_uid) = owner_uid { if target_uid != owner_uid { - server_emitter.emit(ServerEvent::Possess(owner_uid, target_uid)); + emitters.emit(PossessEvent(owner_uid, target_uid)); } } }, diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index fa4fb4dd65..29e2c38292 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -6,7 +6,12 @@ use common::{ Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori, PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, }, - event::{EventBus, ServerEvent}, + event::{ + BuffEvent, ComboChangeEvent, DeleteEvent, EmitExt, EnergyChangeEvent, + EntityAttackedHookEvent, EventBus, HealthChangeEvent, KnockbackEvent, MineBlockEvent, + ParryHookEvent, PoiseChangeEvent, SoundEvent, + }, + event_emitters, outcome::Outcome, resources::{DeltaTime, Time}, uid::{IdMaps, Uid}, @@ -18,10 +23,26 @@ use rand::Rng; use specs::{shred, Entities, Join, LendJoin, Read, ReadStorage, SystemData, WriteStorage}; use vek::*; +event_emitters! { + struct Events[Emitters] { + health_change: HealthChangeEvent, + energy_change: EnergyChangeEvent, + poise_change: PoiseChangeEvent, + sound: SoundEvent, + mine_block: MineBlockEvent, + parry_hook: ParryHookEvent, + kockback: KnockbackEvent, + entity_attack_hoow: EntityAttackedHookEvent, + combo_change: ComboChangeEvent, + buff: BuffEvent, + delete: DeleteEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, - server_bus: Read<'a, EventBus>, + events: Events<'a>, time: Read<'a, Time>, players: ReadStorage<'a, Player>, dt: Read<'a, DeltaTime>, @@ -63,7 +84,7 @@ impl<'a> System<'a> for Sys { _job: &mut Job, (read_data, mut shockwaves, mut shockwave_hit_lists, outcomes): Self::SystemData, ) { - let mut server_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); let mut outcomes_emitter = outcomes.emitter(); let mut rng = rand::thread_rng(); @@ -93,7 +114,7 @@ impl<'a> System<'a> for Sys { .and_then(|uid| read_data.id_maps.uid_entity(uid)); if rng.gen_bool(0.05) { - server_emitter.emit(ServerEvent::Sound { + emitters.emit(SoundEvent { sound: Sound::new(SoundKind::Shockwave, pos.0, 40.0, time), }); } @@ -101,7 +122,7 @@ impl<'a> System<'a> for Sys { // If shockwave is out of time emit destroy event but still continue since it // may have traveled and produced effects a bit before reaching it's end point if time > end_time { - server_emitter.emit(ServerEvent::Delete(entity)); + emitters.emit(DeleteEvent(entity)); continue; } @@ -249,7 +270,7 @@ impl<'a> System<'a> for Sys { 1.0, shockwave.dodgeable.to_attack_source(), *read_data.time, - |e| server_emitter.emit(e), + &mut emitters, |o| outcomes_emitter.emit(o), &mut rng, 0, diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 8dd19cf102..2758e38f8e 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -7,7 +7,8 @@ use common::{ Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats, StatsModifier, }, - event::{EventBus, ServerEvent}, + event::{DestroyEvent, EmitExt}, + event_emitters, resources::{DeltaTime, EntitiesDiedLastTick, Time}, }; use common_ecs::{Job, Origin, Phase, System}; @@ -19,12 +20,18 @@ const ENERGY_REGEN_ACCEL: f32 = 1.0; const SIT_ENERGY_REGEN_ACCEL: f32 = 2.5; const POISE_REGEN_ACCEL: f32 = 2.0; +event_emitters! { + struct Events[Emitters] { + destroy: DestroyEvent, + } +} + #[derive(SystemData)] pub struct ReadData<'a> { entities: Entities<'a>, dt: Read<'a, DeltaTime>, time: Read<'a, Time>, - server_bus: Read<'a, EventBus>, + events: Events<'a>, positions: ReadStorage<'a, Pos>, bodies: ReadStorage<'a, Body>, char_states: ReadStorage<'a, CharacterState>, @@ -65,7 +72,7 @@ impl<'a> System<'a> for Sys { ): Self::SystemData, ) { entities_died_last_tick.0.clear(); - let mut server_event_emitter = read_data.server_bus.emitter(); + let mut emitters = read_data.events.get_emitters(); let dt = read_data.dt.0; // Update stats @@ -84,7 +91,7 @@ impl<'a> System<'a> for Sys { if set_dead { let cloned_entity = (entity, *pos); entities_died_last_tick.0.push(cloned_entity); - server_event_emitter.emit(ServerEvent::Destroy { + emitters.emit(DestroyEvent { entity, cause: health.last_change, }); diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 29c38d93bb..d607251a98 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -3,7 +3,7 @@ use crate::{ AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST, PARTIAL_PATH_DIST, SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE, }, - data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData}, + data::{AgentData, AgentEmitters, AttackData, Path, ReadData, Tactic, TargetData}, util::{ aim_projectile, are_our_owners_hostile, entities_have_line_of_sight, get_attacker, get_entity_by_id, is_dead_or_invulnerable, is_dressed_as_cultist, is_invulnerable, @@ -28,7 +28,7 @@ use common::{ }, consts::MAX_MOUNT_RANGE, effect::{BuffEffect, Effect}, - event::{Emitter, ServerEvent}, + event::{ChatEvent, EmitExt, SoundEvent}, mounting::VolumePos, path::TraversalConfig, rtsim::NpcActivity, @@ -180,7 +180,7 @@ impl<'a> AgentData<'a> { agent: &mut Agent, controller: &mut Controller, read_data: &ReadData, - event_emitter: &mut Emitter, + emitters: &mut AgentEmitters, rng: &mut impl Rng, ) { enum ActionTimers { @@ -430,7 +430,7 @@ impl<'a> AgentData<'a> { agent, controller, read_data, - event_emitter, + emitters, AgentData::is_hunting_animal, ); } @@ -807,7 +807,7 @@ impl<'a> AgentData<'a> { agent: &mut Agent, controller: &mut Controller, read_data: &ReadData, - event_emitter: &mut Emitter, + emitters: &mut AgentEmitters, is_enemy: fn(&Self, EcsEntity, &ReadData) -> bool, ) { enum ActionStateTimers { @@ -856,7 +856,7 @@ impl<'a> AgentData<'a> { self.chat_npc_if_allowed_to_speak( Content::localized("npc-speech-ambush"), agent, - event_emitter, + emitters, ); aggro_on = true; Some((entity, true)) @@ -1666,13 +1666,13 @@ impl<'a> AgentData<'a> { agent: &mut Agent, controller: &mut Controller, read_data: &ReadData, - event_emitter: &mut Emitter, + emitters: &mut AgentEmitters, rng: &mut impl Rng, ) { agent.forget_old_sounds(read_data.time.0); if is_invulnerable(*self.entity, read_data) { - self.idle(agent, controller, read_data, event_emitter, rng); + self.idle(agent, controller, read_data, emitters, rng); return; } @@ -1705,13 +1705,13 @@ impl<'a> AgentData<'a> { } else if self.below_flee_health(agent) || !follows_threatening_sounds { self.flee(agent, controller, read_data, &sound_pos); } else { - self.idle(agent, controller, read_data, event_emitter, rng); + self.idle(agent, controller, read_data, emitters, rng); } } else { - self.idle(agent, controller, read_data, event_emitter, rng); + self.idle(agent, controller, read_data, emitters, rng); } } else { - self.idle(agent, controller, read_data, event_emitter, rng); + self.idle(agent, controller, read_data, emitters, rng); } } @@ -1720,7 +1720,7 @@ impl<'a> AgentData<'a> { agent: &mut Agent, read_data: &ReadData, controller: &mut Controller, - event_emitter: &mut Emitter, + emitters: &mut AgentEmitters, rng: &mut impl Rng, ) { if let Some(Target { target, .. }) = agent.target { @@ -1750,7 +1750,7 @@ impl<'a> AgentData<'a> { Some(tgt_pos.0), )); - self.idle(agent, controller, read_data, event_emitter, rng); + self.idle(agent, controller, read_data, emitters, rng); } else { let target_data = TargetData::new(tgt_pos, target, read_data); // TODO: Reimplement this in rtsim @@ -1774,25 +1774,23 @@ impl<'a> AgentData<'a> { &self, msg: Content, agent: &Agent, - event_emitter: &mut Emitter<'_, ServerEvent>, + emitters: &mut AgentEmitters, ) -> bool { if agent.allowed_to_speak() { - self.chat_npc(msg, event_emitter); + self.chat_npc(msg, emitters); true } else { false } } - pub fn chat_npc(&self, content: Content, event_emitter: &mut Emitter<'_, ServerEvent>) { - event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( - *self.uid, content, - ))); + pub fn chat_npc(&self, content: Content, emitters: &mut AgentEmitters) { + emitters.emit(ChatEvent(UnresolvedChatMsg::npc(*self.uid, content))); } - fn emit_scream(&self, time: f64, event_emitter: &mut Emitter<'_, ServerEvent>) { + fn emit_scream(&self, time: f64, emitters: &mut AgentEmitters) { if let Some(body) = self.body { - event_emitter.emit(ServerEvent::Sound { + emitters.emit(SoundEvent { sound: Sound::new( SoundKind::Utterance(UtteranceKind::Scream, *body), self.pos.0, @@ -1803,12 +1801,7 @@ impl<'a> AgentData<'a> { } } - pub fn cry_out( - &self, - agent: &Agent, - event_emitter: &mut Emitter<'_, ServerEvent>, - read_data: &ReadData, - ) { + pub fn cry_out(&self, agent: &Agent, emitters: &mut AgentEmitters, read_data: &ReadData) { let has_enemy_alignment = matches!(self.alignment, Some(Alignment::Enemy)); if has_enemy_alignment { @@ -1817,28 +1810,24 @@ impl<'a> AgentData<'a> { self.chat_npc_if_allowed_to_speak( Content::localized("npc-speech-cultist_low_health_fleeing"), agent, - event_emitter, + emitters, ); } else if is_villager(self.alignment) { self.chat_npc_if_allowed_to_speak( Content::localized("npc-speech-villager_under_attack"), agent, - event_emitter, + emitters, ); - self.emit_scream(read_data.time.0, event_emitter); + self.emit_scream(read_data.time.0, emitters); } } - pub fn exclaim_relief_about_enemy_dead( - &self, - agent: &Agent, - event_emitter: &mut Emitter<'_, ServerEvent>, - ) { + pub fn exclaim_relief_about_enemy_dead(&self, agent: &Agent, emitters: &mut AgentEmitters) { if is_villager(self.alignment) { self.chat_npc_if_allowed_to_speak( Content::localized("npc-speech-villager_enemy_killed"), agent, - event_emitter, + emitters, ); } } @@ -2002,7 +1991,7 @@ impl<'a> AgentData<'a> { controller: &mut Controller, target: EcsEntity, read_data: &ReadData, - event_emitter: &mut Emitter, + emitters: &mut AgentEmitters, rng: &mut impl Rng, remembers_fight_with_target: bool, ) { @@ -2011,7 +2000,7 @@ impl<'a> AgentData<'a> { let move_dir_mag = move_dir.magnitude(); let small_chance = rng.gen::() < read_data.dt.0 * 0.25; let mut chat = |content: Content| { - self.chat_npc_if_allowed_to_speak(content, agent, event_emitter); + self.chat_npc_if_allowed_to_speak(content, agent, emitters); }; let mut chat_villager_remembers_fighting = || { let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone()); diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index 384485a033..c39cbc06b1 100755 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -17,6 +17,7 @@ use common::{ Stats, Vel, }, consts::GRAVITY, + event, event_emitters, link::Is, mounting::{Mount, Rider, VolumeRider}, path::TraversalConfig, @@ -28,6 +29,14 @@ use common::{ }; use specs::{shred, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData}; +event_emitters! { + pub struct AgentEvents[AgentEmitters] { + chat: event::ChatEvent, + sound: event::SoundEvent, + process_trade_action: event::ProcessTradeActionEvent, + } +} + // TODO: Move rtsim back into AgentData after rtsim2 when it has a separate // crate pub struct AgentData<'a> { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 154c5ebec7..efe48ae1d2 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -40,7 +40,10 @@ use common::{ }, depot, effect::Effect, - event::{EventBus, ServerEvent}, + event::{ + ClientDisconnectEvent, CreateWaypointEvent, EventBus, ExplosionEvent, GroupManipEvent, + InitiateInviteEvent, TamePetEvent, + }, generation::{EntityConfig, EntityInfo}, link::Is, mounting::{Rider, Volume, VolumeRider}, @@ -490,6 +493,7 @@ fn handle_drop_all( pos.0.y + rng.gen_range(5.0..10.0), pos.0.z + 5.0, )), + comp::Ori::default(), comp::Vel(vel), item, None, @@ -1804,9 +1808,7 @@ fn handle_spawn( // Add to group system if a pet if matches!(alignment, comp::Alignment::Owned { .. }) { - let server_eventbus = - server.state.ecs().read_resource::>(); - server_eventbus.emit_now(ServerEvent::TamePet { + server.state.emit_event_now(TamePetEvent { owner_entity: target, pet_entity: new_entity, }); @@ -2079,8 +2081,8 @@ fn handle_spawn_campfire( server .state .ecs() - .read_resource::>() - .emit_now(ServerEvent::CreateWaypoint(pos.0)); + .read_resource::>() + .emit_now(CreateWaypointEvent(pos.0)); server.notify_client( client, @@ -2907,26 +2909,23 @@ fn handle_explosion( .read_storage::() .get(target) .copied(); - server - .state - .mut_resource::>() - .emit_now(ServerEvent::Explosion { - pos: pos.0, - explosion: Explosion { - effects: vec![ - RadiusEffect::Entity(Effect::Damage(Damage { - source: DamageSource::Explosion, - kind: DamageKind::Energy, - value: 100.0 * power, - })), - RadiusEffect::TerrainDestruction(power, Rgb::black()), - ], - radius: 3.0 * power, - reagent: None, - min_falloff: 0.0, - }, - owner, - }); + server.state.emit_event_now(ExplosionEvent { + pos: pos.0, + explosion: Explosion { + effects: vec![ + RadiusEffect::Entity(Effect::Damage(Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: 100.0 * power, + })), + RadiusEffect::TerrainDestruction(power, Rgb::black()), + ], + radius: 3.0 * power, + reagent: None, + min_falloff: 0.0, + }, + owner, + }); Ok(()) } @@ -3261,8 +3260,7 @@ fn handle_group_invite( server .state - .mut_resource::>() - .emit_now(ServerEvent::InitiateInvite(target, uid, InviteKind::Group)); + .emit_event_now(InitiateInviteEvent(target, uid, InviteKind::Group)); if client != target { server.notify_client( @@ -3301,8 +3299,7 @@ fn handle_group_kick( server .state - .mut_resource::>() - .emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Kick(uid))); + .emit_event_now(GroupManipEvent(target, comp::GroupManip::Kick(uid))); Ok(()) } else { Err(Content::Plain(action.help_string())) @@ -3318,8 +3315,7 @@ fn handle_group_leave( ) -> CmdResult<()> { server .state - .mut_resource::>() - .emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Leave)); + .emit_event_now(GroupManipEvent(target, comp::GroupManip::Leave)); Ok(()) } @@ -3337,11 +3333,7 @@ fn handle_group_promote( server .state - .mut_resource::>() - .emit_now(ServerEvent::GroupManip( - target, - comp::GroupManip::AssignLeader(uid), - )); + .emit_event_now(GroupManipEvent(target, comp::GroupManip::AssignLeader(uid))); Ok(()) } else { Err(Content::Plain(action.help_string())) @@ -3944,8 +3936,8 @@ fn kick_player( ); server .state - .mut_resource::>() - .emit_now(ServerEvent::ClientDisconnect( + .mut_resource::>() + .emit_now(ClientDisconnectEvent( target_player, comp::DisconnectReason::Kicked, )); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 15137a001a..4206ea8742 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -3,25 +3,24 @@ use crate::{ presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt, }; use common::{ - character::CharacterId, comp::{ self, aura::{Aura, AuraKind, AuraTarget}, buff::{BuffCategory, BuffData, BuffKind, BuffSource}, - misc::PortalData, ship::figuredata::VOXEL_COLLIDER_MANIFEST, - shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos, - Projectile, TradingBehavior, Vel, WaypointArea, + Alignment, BehaviorCapability, ItemDrops, LightEmitter, Ori, Pos, TradingBehavior, Vel, + WaypointArea, + }, + event::{ + CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent, CreateShipEvent, + CreateTeleporterEvent, CreateWaypointEvent, EventBus, InitializeCharacterEvent, + InitializeSpectatorEvent, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent, }, - event::{EventBus, NpcBuilder, UpdateCharacterMetadata}, mounting::{Mounting, Volume, VolumeMounting, VolumePos}, outcome::Outcome, resources::{Secs, Time}, - rtsim::RtSimEntity, uid::{IdMaps, Uid}, - util::Dir, vol::IntoFullVolIterator, - ViewDistances, }; use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; use specs::{Builder, Entity as EcsEntity, WorldExt}; @@ -29,56 +28,57 @@ use vek::{Rgb, Vec3}; use super::group_manip::update_map_markers; -pub fn handle_initialize_character( - server: &mut Server, - entity: EcsEntity, - character_id: CharacterId, - requested_view_distances: ViewDistances, -) { +pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) { let updater = server.state.ecs().fetch::(); - let pending_database_action = updater.has_pending_database_action(character_id); + let pending_database_action = updater.has_pending_database_action(ev.character_id); drop(updater); if !pending_database_action { - let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance); + let clamped_vds = ev + .requested_view_distances + .clamp(server.settings().max_view_distance); server .state - .initialize_character_data(entity, character_id, clamped_vds); + .initialize_character_data(ev.entity, ev.character_id, clamped_vds); // Correct client if its requested VD is too high. - if requested_view_distances.terrain != clamped_vds.terrain { - server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain)); + if ev.requested_view_distances.terrain != clamped_vds.terrain { + server.notify_client( + ev.entity, + ServerGeneral::SetViewDistance(clamped_vds.terrain), + ); } } else { // A character delete or update was somehow initiated after the login commenced, // so kick the client out of "ingame" without saving any data and abort // the character loading process. - handle_exit_ingame(server, entity, true); + handle_exit_ingame(server, ev.entity, true); } } -pub fn handle_initialize_spectator( - server: &mut Server, - entity: EcsEntity, - requested_view_distances: ViewDistances, -) { - let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance); - server.state.initialize_spectator_data(entity, clamped_vds); +pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) { + let clamped_vds = ev.1.clamp(server.settings().max_view_distance); + server.state.initialize_spectator_data(ev.0, clamped_vds); // Correct client if its requested VD is too high. - if requested_view_distances.terrain != clamped_vds.terrain { - server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain)); + if ev.1.terrain != clamped_vds.terrain { + server.notify_client(ev.0, ServerGeneral::SetViewDistance(clamped_vds.terrain)); } - sys::subscription::initialize_region_subscription(server.state.ecs(), entity); + sys::subscription::initialize_region_subscription(server.state.ecs(), ev.0); } -pub fn handle_loaded_character_data( - server: &mut Server, - entity: EcsEntity, - loaded_components: PersistedComponents, - metadata: UpdateCharacterMetadata, -) { +pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) { + let loaded_components = PersistedComponents { + body: ev.components.0, + stats: ev.components.1, + skill_set: ev.components.2, + inventory: ev.components.3, + waypoint: ev.components.4, + pets: ev.components.5, + active_abilities: ev.components.6, + map_marker: ev.components.7, + }; if let Some(marker) = loaded_components.map_marker { server.notify_client( - entity, + ev.entity, ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update( marker.0, ))), @@ -87,68 +87,62 @@ pub fn handle_loaded_character_data( let result_msg = if let Err(err) = server .state - .update_character_data(entity, loaded_components) + .update_character_data(ev.entity, loaded_components) { - handle_exit_ingame(server, entity, false); // remove client from in-game state + handle_exit_ingame(server, ev.entity, false); // remove client from in-game state ServerGeneral::CharacterDataLoadResult(Err(err)) } else { - sys::subscription::initialize_region_subscription(server.state.ecs(), entity); + sys::subscription::initialize_region_subscription(server.state.ecs(), ev.entity); // We notify the client with the metadata result from the operation. - ServerGeneral::CharacterDataLoadResult(Ok(metadata)) + ServerGeneral::CharacterDataLoadResult(Ok(ev.metadata)) }; - server.notify_client(entity, result_msg); + server.notify_client(ev.entity, result_msg); } -pub fn handle_create_npc( - server: &mut Server, - pos: Pos, - ori: Ori, - mut npc: NpcBuilder, - rider: Option, -) -> EcsEntity { +pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEntity { let entity = server .state .create_npc( - pos, - ori, - npc.stats, - npc.skill_set, - npc.health, - npc.poise, - npc.inventory, - npc.body, + ev.pos, + ev.ori, + ev.npc.stats, + ev.npc.skill_set, + ev.npc.health, + ev.npc.poise, + ev.npc.inventory, + ev.npc.body, ) - .with(npc.scale); + .with(ev.npc.scale); - if let Some(agent) = &mut npc.agent { - if let Alignment::Owned(_) = &npc.alignment { + if let Some(agent) = &mut ev.npc.agent { + if let Alignment::Owned(_) = &ev.npc.alignment { agent.behavior.allow(BehaviorCapability::TRADE); agent.behavior.trading_behavior = TradingBehavior::AcceptFood; } } - let entity = entity.with(npc.alignment); + let entity = entity.with(ev.npc.alignment); - let entity = if let Some(agent) = npc.agent { + let entity = if let Some(agent) = ev.npc.agent { entity.with(agent) } else { entity }; - let entity = if let Some(drop_items) = npc.loot.to_items() { + let entity = if let Some(drop_items) = ev.npc.loot.to_items() { entity.with(ItemDrops(drop_items)) } else { entity }; - let entity = if let Some(home_chunk) = npc.anchor { + let entity = if let Some(home_chunk) = ev.npc.anchor { entity.with(home_chunk) } else { entity }; // Rtsim entity added to IdMaps below. - let entity = if let Some(rtsim_entity) = npc.rtsim_entity { + let entity = if let Some(rtsim_entity) = ev.npc.rtsim_entity { entity.with(rtsim_entity).with(RepositionOnChunkLoad { needs_ground: false, }) @@ -156,7 +150,7 @@ pub fn handle_create_npc( entity }; - let entity = if let Some(projectile) = npc.projectile { + let entity = if let Some(projectile) = ev.npc.projectile { entity.with(projectile) } else { entity @@ -164,7 +158,7 @@ pub fn handle_create_npc( let new_entity = entity.build(); - if let Some(rtsim_entity) = npc.rtsim_entity { + if let Some(rtsim_entity) = ev.npc.rtsim_entity { server .state() .ecs() @@ -173,7 +167,7 @@ pub fn handle_create_npc( } // Add to group system if a pet - if let comp::Alignment::Owned(owner_uid) = npc.alignment { + if let comp::Alignment::Owned(owner_uid) = ev.npc.alignment { let state = server.state(); let uids = state.ecs().read_storage::(); let clients = state.ecs().read_storage::(); @@ -204,7 +198,7 @@ pub fn handle_create_npc( }, ); } - } else if let Some(group) = match npc.alignment { + } else if let Some(group) = match ev.npc.alignment { Alignment::Wild => None, Alignment::Passive => None, Alignment::Enemy => Some(comp::group::ENEMY), @@ -214,8 +208,13 @@ pub fn handle_create_npc( let _ = server.state.ecs().write_storage().insert(new_entity, group); } - if let Some(rider) = rider { - let rider_entity = handle_create_npc(server, pos, Ori::default(), rider, None); + if let Some(rider) = ev.rider { + let rider_entity = handle_create_npc(server, CreateNpcEvent { + pos: ev.pos, + ori: Ori::default(), + npc: rider, + rider: None, + }); let uids = server.state().ecs().read_storage::(); let link = Mounting { mount: *uids.get(new_entity).expect("We just created this entity"), @@ -231,21 +230,13 @@ pub fn handle_create_npc( new_entity } -pub fn handle_create_ship( - server: &mut Server, - pos: Pos, - ori: Ori, - ship: comp::ship::Body, - rtsim_entity: Option, - driver: Option, - passengers: Vec, -) { - let collider = ship.make_collider(); +pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) { + let collider = ev.ship.make_collider(); let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read(); // TODO: Find better solution for this, maybe something like a serverside block // of interests. - let (mut steering, mut seats) = { + let (mut steering, mut _seats) = { let mut steering = Vec::new(); let mut seats = Vec::new(); @@ -263,7 +254,9 @@ pub fn handle_create_ship( (steering.into_iter(), seats.into_iter()) }; - let mut entity = server.state.create_ship(pos, ori, ship, |_| collider); + let mut entity = server + .state + .create_ship(ev.pos, ev.ori, ev.ship, |_| collider); /* if let Some(mut agent) = agent { let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship)); @@ -273,13 +266,18 @@ pub fn handle_create_ship( entity = entity.with(agent); } */ - if let Some(rtsim_vehicle) = rtsim_entity { + if let Some(rtsim_vehicle) = ev.rtsim_entity { entity = entity.with(rtsim_vehicle); } let entity = entity.build(); - if let Some(driver) = driver { - let npc_entity = handle_create_npc(server, pos, ori, driver, None); + if let Some(driver) = ev.driver { + let npc_entity = handle_create_npc(server, CreateNpcEvent { + pos: ev.pos, + ori: ev.ori, + npc: driver, + rider: None, + }); let uids = server.state.ecs().read_storage::(); let (rider_uid, mount_uid) = uids @@ -312,14 +310,14 @@ pub fn handle_create_ship( } } - for passenger in passengers { - let npc_entity = handle_create_npc( - server, - Pos(pos.0 + Vec3::unit_z() * 5.0), - ori, - passenger, - None, - ); + /* + for passenger in ev.passengers { + let npc_entity = handle_create_npc(server, CreateNpcEvent { + pos: Pos(ev.pos.0 + Vec3::unit_z() * 5.0), + ori: ev.ori, + npc: passenger, + rider: None, + }); if let Some((rider_pos, rider_block)) = seats.next() { let uids = server.state.ecs().read_storage::(); let (rider_uid, mount_uid) = uids @@ -342,62 +340,54 @@ pub fn handle_create_ship( .expect("Failed to link passanger to ship"); } } + */ } -pub fn handle_shoot( - server: &mut Server, - entity: EcsEntity, - pos: Pos, - dir: Dir, - body: Body, - light: Option, - projectile: Projectile, - speed: f32, - object: Option, -) { +pub fn handle_shoot(server: &mut Server, ev: ShootEvent) { let state = server.state_mut(); - let pos = pos.0; + let pos = ev.pos.0; - let vel = *dir * speed + let vel = *ev.dir * ev.speed + state .ecs() .read_storage::() - .get(entity) + .get(ev.entity) .map_or(Vec3::zero(), |v| v.0); // Add an outcome state .ecs() .read_resource::>() - .emit_now(Outcome::ProjectileShot { pos, body, vel }); + .emit_now(Outcome::ProjectileShot { + pos, + body: ev.body, + vel, + }); - let mut builder = state.create_projectile(Pos(pos), Vel(vel), body, projectile); - if let Some(light) = light { + let mut builder = state.create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile); + if let Some(light) = ev.light { builder = builder.with(light) } - if let Some(object) = object { + if let Some(object) = ev.object { builder = builder.with(object) } builder.build(); } -pub fn handle_shockwave( - server: &mut Server, - properties: shockwave::Properties, - pos: Pos, - ori: Ori, -) { +pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) { let state = server.state_mut(); - state.create_shockwave(properties, pos, ori).build(); + state + .create_shockwave(ev.properties, ev.pos, ev.ori) + .build(); } -pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { +pub fn handle_create_waypoint(server: &mut Server, ev: CreateWaypointEvent) { let time = server.state.get_time(); server .state - .create_object(Pos(pos), comp::object::Body::CampfireLit) + .create_object(Pos(ev.0), comp::object::Body::CampfireLit) .with(LightEmitter { col: Rgb::new(1.0, 0.3, 0.1), strength: 5.0, @@ -435,9 +425,40 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { .build(); } -pub fn handle_create_teleporter(server: &mut Server, pos: Vec3, portal: PortalData) { +pub fn handle_create_teleporter(server: &mut Server, ev: CreateTeleporterEvent) { server .state - .create_teleporter(comp::Pos(pos), portal) + .create_teleporter(comp::Pos(ev.0), ev.1) + .build(); +} + +pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) { + println!("Handle item drop event."); + dbg!(ev.vel); + server + .state + .create_item_drop(ev.pos, ev.ori, ev.vel, ev.item, ev.loot_owner); +} + +pub fn handle_create_object( + server: &mut Server, + CreateObjectEvent { + pos, + vel, + body, + object, + item, + light_emitter, + stats, + }: CreateObjectEvent, +) { + server + .state + .create_object(pos, body) + .with(vel) + .maybe_with(object) + .maybe_with(item) + .maybe_with(light_emitter) + .maybe_with(stats) .build(); } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 2ac3893302..566793cf0d 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,17 +1,17 @@ use crate::{ client::Client, comp::{ - ability, agent::{Agent, AgentEvent, Sound, SoundKind}, loot_owner::LootOwner, skillset::SkillGroupKind, BuffKind, BuffSource, PhysicsState, }, - rtsim, + error, + rtsim::RtSim, + state_ext::StateExt, sys::terrain::SAFE_ZONE_RADIUS, - Server, SpawnPoint, StateExt, + Server, Settings, SpawnPoint, }; -use authc::Uuid; use common::{ combat, combat::{AttackSource, DamageContributor}, @@ -21,14 +21,25 @@ use common::{ inventory::item::{AbilityMap, MaterialStatManifest}, item::flatten_counted_items, loot_owner::LootOwnerKind, - Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory, - Object, Player, Poise, Pos, SkillSet, Stats, + Alignment, Auras, Body, CharacterState, Energy, Group, Health, Inventory, Object, Player, + Poise, Pos, Presence, PresenceKind, SkillSet, Stats, }, consts::TELEPORTER_RADIUS, - event::{EventBus, ServerEvent}, + event::{ + AuraEvent, BonkEvent, BuffEvent, ChangeAbilityEvent, ChangeBodyEvent, ChangeStanceEvent, + ChatEvent, ComboChangeEvent, CreateItemDropEvent, CreateObjectEvent, DeleteEvent, + DestroyEvent, EmitExt, Emitter, EnergyChangeEvent, EntityAttackedHookEvent, EventBus, + ExplosionEvent, HealthChangeEvent, KnockbackEvent, LandOnGroundEvent, MakeAdminEvent, + ParryHookEvent, PoiseChangeEvent, RemoveLightEmitterEvent, RespawnEvent, SoundEvent, + StartTeleportingEvent, TeleportToEvent, TeleportToPositionEvent, UpdateMapMarkerEvent, + }, + event_emitters, + link::Is, lottery::distribute_many, + mounting::{Rider, VolumeRider}, outcome::{HealthChangeInfo, Outcome}, resources::{Secs, Time}, + rtsim::{Actor, RtSimEntity}, spiral::Spiral2d, states::utils::StageSection, terrain::{Block, BlockKind, TerrainGrid}, @@ -36,16 +47,55 @@ use common::{ uid::{IdMaps, Uid}, util::Dir, vol::ReadVol, - Damage, DamageKind, DamageSource, Explosion, GroupTarget, RadiusEffect, + CachedSpatialGrid, Damage, DamageKind, DamageSource, GroupTarget, RadiusEffect, }; -use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; +use common_net::msg::ServerGeneral; use common_state::{AreasContainer, BlockChange, NoDurabilityArea}; use hashbrown::HashSet; use rand::Rng; -use specs::{Builder, Entity as EcsEntity, Entity, Join, LendJoin, WorldExt}; +use specs::{ + shred, DispatcherBuilder, Entities, Entity as EcsEntity, Entity, Join, LendJoin, Read, + ReadExpect, ReadStorage, SystemData, Write, WriteExpect, WriteStorage, +}; use std::{collections::HashMap, iter, sync::Arc, time::Duration}; -use tracing::{debug, error, warn}; +use tracing::{debug, warn}; use vek::{Vec2, Vec3}; +use world::World; + +use super::{event_dispatch, ServerEvent}; + +pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) { + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); + event_dispatch::(builder); +} + +pub fn handle_delete(server: &mut Server, DeleteEvent(entity): DeleteEvent) { + let _ = server + .state_mut() + .delete_entity_recorded(entity) + .map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity")); +} #[derive(Hash, Eq, PartialEq)] enum DamageContrib { @@ -54,1252 +104,122 @@ enum DamageContrib { NotFound, } -pub fn handle_poise(server: &Server, entity: EcsEntity, change: comp::PoiseChange) { - let ecs = &server.state.ecs(); - if let Some(character_state) = ecs.read_storage::().get(entity) { - // Entity is invincible to poise change during stunned character state - if !matches!(character_state, CharacterState::Stunned(_)) { - if let Some(mut poise) = ecs.write_storage::().get_mut(entity) { - poise.change(change); - } - } - } -} +impl ServerEvent for PoiseChangeEvent { + type SystemData<'a> = ( + Entities<'a>, + ReadStorage<'a, CharacterState>, + WriteStorage<'a, Poise>, + ); -pub fn handle_health_change(server: &Server, entity: EcsEntity, change: HealthChange) { - let ecs = &server.state.ecs(); - if let Some(mut health) = ecs.write_storage::().get_mut(entity) { - // If the change amount was not zero - let changed = health.change_by(change); - if let (Some(pos), Some(uid)) = ( - ecs.read_storage::().get(entity), - ecs.read_storage::().get(entity), - ) { - if changed { - let outcomes = ecs.write_resource::>(); - outcomes.emit_now(Outcome::HealthChange { - pos: pos.0, - info: HealthChangeInfo { - amount: change.amount, - by: change.by, - target: *uid, - cause: change.cause, - precise: change.precise, - instance: change.instance, - }, - }); - } - } - } - // This if statement filters out anything under 5 damage, for DOT ticks - // TODO: Find a better way to separate direct damage from DOT here - let damage = -change.amount; - if damage > 5.0 { - if let Some(agent) = ecs.write_storage::().get_mut(entity) { - agent.inbox.push_back(AgentEvent::Hurt); - } - } -} - -pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3) { - let ecs = &server.state.ecs(); - let clients = ecs.read_storage::(); - - if let Some(physics) = ecs.read_storage::().get(entity) { - //Check if the entity is on a surface. If it is not, reduce knockback. - let mut impulse = impulse - * if physics.on_surface().is_some() { - 1.0 - } else { - 0.4 - }; - if let Some(mass) = ecs.read_storage::().get(entity) { - // we go easy on the little ones (because they fly so far) - impulse /= mass.0.max(40.0); - } - let mut velocities = ecs.write_storage::(); - if let Some(vel) = velocities.get_mut(entity) { - vel.0 += impulse; - } - if let Some(client) = clients.get(entity) { - client.send_fallible(ServerGeneral::Knockback(impulse)); - } - } -} - -/// Handle an entity dying. If it is a player, it will send a message to all -/// other players. If the entity that killed it had stats, then give it exp for -/// the kill. Experience given is equal to the level of the entity that was -/// killed times 10. -pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: HealthChange) { - let state = server.state_mut(); - - // TODO: Investigate duplicate `Destroy` events (but don't remove this). - // If the entity was already deleted, it can't be destroyed again. - if !state.ecs().is_alive(entity) { - return; - } - - // Remove components that should not persist across death - state.ecs().write_storage::().remove(entity); - state.ecs().write_storage::().remove(entity); - - let get_attacker_name = |cause_of_death: KillType, by: Uid| -> KillSource { - // Get attacker entity - if let Some(char_entity) = state.ecs().entity_from_uid(by) { - // Check if attacker is another player or entity with stats (npc) - if state - .ecs() - .read_storage::() - .get(char_entity) - .is_some() + fn handle( + events: impl ExactSizeIterator, + (entities, character_states, mut poises): Self::SystemData<'_>, + ) { + for ev in events { + if let Some((character_state, mut poise)) = (&character_states, &mut poises) + .lend_join() + .get(ev.entity, &entities) { - KillSource::Player(by, cause_of_death) - } else if let Some(stats) = state.ecs().read_storage::().get(char_entity) { - KillSource::NonPlayer(stats.name.clone(), cause_of_death) - } else { - KillSource::NonExistent(cause_of_death) - } - } else { - KillSource::NonExistent(cause_of_death) - } - }; - - // Push an outcome if entity is has a character state (entities that don't have - // one, we probably don't care about emitting death outcome) - if state - .ecs() - .read_storage::() - .get(entity) - .is_some() - { - if let Some(pos) = state.ecs().read_storage::().get(entity) { - state - .ecs() - .read_resource::>() - .emit_now(Outcome::Death { pos: pos.0 }); - } - } - - // Chat message - // If it was a player that died - if let Some(_player) = state.ecs().read_storage::().get(entity) { - if let Some(uid) = state.ecs().read_storage::().get(entity) { - let kill_source = match (last_change.cause, last_change.by.map(|x| x.uid())) { - (Some(DamageSource::Melee), Some(by)) => get_attacker_name(KillType::Melee, by), - (Some(DamageSource::Projectile), Some(by)) => { - get_attacker_name(KillType::Projectile, by) - }, - (Some(DamageSource::Explosion), Some(by)) => { - get_attacker_name(KillType::Explosion, by) - }, - (Some(DamageSource::Energy), Some(by)) => get_attacker_name(KillType::Energy, by), - (Some(DamageSource::Buff(buff_kind)), by) => { - if let Some(by) = by { - get_attacker_name(KillType::Buff(buff_kind), by) - } else { - KillSource::NonExistent(KillType::Buff(buff_kind)) - } - }, - (Some(DamageSource::Other), Some(by)) => get_attacker_name(KillType::Other, by), - (Some(DamageSource::Falling), _) => KillSource::FallDamage, - // HealthSource::Suicide => KillSource::Suicide, - _ => KillSource::Other, - }; - - state.send_chat(comp::ChatType::Kill(kill_source, *uid).into_plain_msg("")); - } - } - - let mut exp_awards = Vec::<(Entity, f32, Option)>::new(); - // Award EXP to damage contributors - // - // NOTE: Debug logging is disabled by default for this module - to enable it add - // veloren_server::events::entity_manipulation=debug to RUST_LOG - (|| { - let mut skill_sets = state.ecs().write_storage::(); - let healths = state.ecs().read_storage::(); - let energies = state.ecs().read_storage::(); - let inventories = state.ecs().read_storage::(); - let players = state.ecs().read_storage::(); - let bodies = state.ecs().read_storage::(); - let poises = state.ecs().read_storage::(); - let positions = state.ecs().read_storage::(); - let groups = state.ecs().read_storage::(); - - let ( - entity_skill_set, - entity_health, - entity_energy, - entity_inventory, - entity_body, - entity_poise, - entity_pos, - ) = match (|| { - Some(( - skill_sets.get(entity)?, - healths.get(entity)?, - energies.get(entity)?, - inventories.get(entity)?, - bodies.get(entity)?, - poises.get(entity)?, - positions.get(entity)?, - )) - })() { - Some(comps) => comps, - None => return, - }; - - // Calculate the total EXP award for the kill - let msm = state.ecs().read_resource::(); - let exp_reward = combat::combat_rating( - entity_inventory, - entity_health, - entity_energy, - entity_poise, - entity_skill_set, - *entity_body, - &msm, - ) * 20.0; - - let mut damage_contributors = HashMap::::new(); - for (damage_contributor, damage) in entity_health.damage_contributions() { - match damage_contributor { - DamageContributor::Solo(uid) => { - if let Some(attacker) = state.ecs().entity_from_uid(*uid) { - damage_contributors.insert(DamageContrib::Solo(attacker), (*damage, 0.0)); - } else { - // An entity who was not in a group contributed damage but is now either - // dead or offline. Add a placeholder to ensure that the contributor's exp - // is discarded, not distributed between the other contributors - damage_contributors.insert(DamageContrib::NotFound, (*damage, 0.0)); - } - }, - DamageContributor::Group { - entity_uid: _, - group, - } => { - // Damage made by entities who were in a group at the time of attack is - // attributed to their group rather than themselves. This allows for all - // members of a group to receive EXP, not just the damage dealers. - let entry = damage_contributors - .entry(DamageContrib::Group(*group)) - .or_insert((0, 0.0)); - entry.0 += damage; - }, - } - } - - // Calculate the percentage of total damage that each DamageContributor - // contributed - let total_damage: f64 = damage_contributors - .values() - .map(|(damage, _)| *damage as f64) - .sum(); - damage_contributors - .iter_mut() - .for_each(|(_, (damage, percentage))| { - *percentage = (*damage as f64 / total_damage) as f32 - }); - - let alignments = state.ecs().read_storage::(); - let uids = state.ecs().read_storage::(); - let mut outcomes = state.ecs().write_resource::>(); - let inventories = state.ecs().read_storage::(); - - let destroyed_group = groups.get(entity); - - let within_range = |attacker_pos: &Pos| { - // Maximum distance that an attacker must be from an entity at the time of its - // death to receive EXP for the kill - const MAX_EXP_DIST: f32 = 150.0; - entity_pos.0.distance_squared(attacker_pos.0) < MAX_EXP_DIST.powi(2) - }; - - let is_pvp_kill = - |attacker: Entity| players.get(entity).is_some() && players.get(attacker).is_some(); - - // Iterate through all contributors of damage for the killed entity, calculating - // how much EXP each contributor should be awarded based on their - // percentage of damage contribution - exp_awards = damage_contributors.iter().filter_map(|(damage_contributor, (_, damage_percent))| { - let contributor_exp = exp_reward * damage_percent; - match damage_contributor { - DamageContrib::Solo(attacker) => { - // No exp for self kills or PvP - if *attacker == entity || is_pvp_kill(*attacker) { return None; } - - // Only give EXP to the attacker if they are within EXP range of the killed entity - positions.get(*attacker).and_then(|attacker_pos| { - if within_range(attacker_pos) { - debug!("Awarding {} exp to individual {:?} who contributed {}% damage to the kill of {:?}", contributor_exp, attacker, *damage_percent * 100.0, entity); - Some(iter::once((*attacker, contributor_exp, None)).collect()) - } else { - None - } - }) - }, - DamageContrib::Group(group) => { - // Don't give EXP to members in the destroyed entity's group - if destroyed_group == Some(group) { return None; } - - // Only give EXP to members of the group that are within EXP range of the killed entity and aren't a pet - let members_in_range = ( - &state.ecs().entities(), - &groups, - &positions, - alignments.maybe(), - &uids, - ) - .join() - .filter_map(|(member_entity, member_group, member_pos, alignment, uid)| { - if *member_group == *group && within_range(member_pos) && !is_pvp_kill(member_entity) && !matches!(alignment, Some(Alignment::Owned(owner)) if owner != uid) { - Some(member_entity) - } else { - None - } - }) - .collect::>(); - - if members_in_range.is_empty() { return None; } - - // Divide EXP reward by square root of number of people in group for group EXP scaling - let exp_per_member = contributor_exp / (members_in_range.len() as f32).sqrt(); - - debug!("Awarding {} exp per member of group ID {:?} with {} members which contributed {}% damage to the kill of {:?}", exp_per_member, group, members_in_range.len(), *damage_percent * 100.0, entity); - Some(members_in_range.into_iter().map(|entity| (entity, exp_per_member, Some(*group))).collect::)>>()) - }, - DamageContrib::NotFound => { - // Discard exp for dead/offline individual damage contributors - None + // Entity is invincible to poise change during stunned character state + if !matches!(character_state, CharacterState::Stunned(_)) { + poise.change(ev.change); } } - }).flatten().collect::)>>(); - - exp_awards.iter().for_each(|(attacker, exp_reward, _)| { - // Process the calculated EXP rewards - if let (Some(mut attacker_skill_set), Some(attacker_uid), Some(attacker_inventory)) = ( - skill_sets.get_mut(*attacker), - uids.get(*attacker), - inventories.get(*attacker), - ) { - handle_exp_gain( - *exp_reward, - attacker_inventory, - &mut attacker_skill_set, - attacker_uid, - &mut outcomes, - ); - } - }); - })(); - - let should_delete = if state - .ecs() - .write_storage::() - .get_mut(entity) - .is_some() - { - state - .ecs() - .write_storage() - .insert(entity, comp::Vel(Vec3::zero())) - .err() - .map(|e| error!(?e, ?entity, "Failed to set zero vel on dead client")); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|force_update| force_update.update()); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|mut energy| { - let energy = &mut *energy; - energy.refresh() - }); - let _ = state - .ecs() - .write_storage::() - .insert(entity, CharacterState::default()); - - false - } else if state.ecs().read_storage::().contains(entity) - && !matches!( - state.ecs().read_storage::().get(entity), - Some(comp::Alignment::Owned(_)) - ) - { - // Only drop loot if entity has agency (not a player), - // and if it is not owned by another entity (not a pet) - - // Decide for a loot drop before turning into a lootbag - - let items = { - let mut item_drops = state.ecs().write_storage::(); - item_drops.remove(entity).map(|comp::ItemDrops(item)| item) - }; - - if let Some(items) = items { - let pos = state.ecs().read_storage::().get(entity).cloned(); - let vel = state.ecs().read_storage::().get(entity).cloned(); - if let Some(pos) = pos { - // Remove entries where zero exp was awarded - this happens because some - // entities like Object bodies don't give EXP. - let mut item_receivers = HashMap::new(); - for (entity, exp, group) in exp_awards { - if exp >= f32::EPSILON { - let loot_owner = if let Some(group) = group { - Some(LootOwnerKind::Group(group)) - } else { - let uid = state - .ecs() - .read_storage::() - .get(entity) - .and_then(|body| { - // Only humanoids are awarded loot ownership - if the winner - // was a non-humanoid NPC the loot will be free-for-all - if matches!(body, Body::Humanoid(_)) { - Some(state.ecs().read_storage::().get(entity).cloned()) - } else { - None - } - }) - .flatten(); - - uid.map(LootOwnerKind::Player) - }; - - *item_receivers.entry(loot_owner).or_insert(0.0) += exp; - } - } - - let mut item_offset_spiral = - Spiral2d::new().map(|offset| offset.as_::() * 0.5); - - let mut spawn_item = |item, loot_owner| { - let offset = item_offset_spiral.next().unwrap_or_default(); - state.create_item_drop( - Pos(pos.0 + Vec3::unit_z() * 0.25 + offset), - vel.unwrap_or(comp::Vel(Vec3::zero())), - item, - if let Some(loot_owner) = loot_owner { - debug!("Assigned UID {loot_owner:?} as the winner for the loot drop"); - Some(LootOwner::new(loot_owner, false)) - } else { - None - }, - ); - }; - - let msm = &MaterialStatManifest::load().read(); - let ability_map = &AbilityMap::load().read(); - if item_receivers.is_empty() { - for item in flatten_counted_items(&items, ability_map, msm) { - spawn_item(item, None) - } - } else { - let mut rng = rand::thread_rng(); - distribute_many( - item_receivers - .iter() - .map(|(loot_owner, weight)| (*weight, *loot_owner)), - &mut rng, - &items, - |(amount, _)| *amount, - |(_, item), loot_owner, count| { - for item in item.stacked_duplicates(ability_map, msm, count) { - spawn_item(item, loot_owner) - } - }, - ); - } - } else { - error!( - ?entity, - "Entity doesn't have a position, no bag is being dropped" - ) - } - } - - true - } else { - true - }; - - let resists_durability = state - .ecs() - .read_storage::() - .get(entity) - .cloned() - .map_or(false, |our_pos| { - let areas_container = state - .ecs() - .read_resource::>(); - let our_pos = our_pos.0.map(|i| i as i32); - - let is_in_area = areas_container - .areas() - .iter() - .any(|(_, area)| area.contains_point(our_pos)); - - is_in_area - }); - - // TODO: Do we need to do this if `should_delete` is true? - // Modify durability on all equipped items - if !resists_durability - && let Some(mut inventory) = state.ecs().write_storage::().get_mut(entity) - { - let ecs = state.ecs(); - let ability_map = ecs.read_resource::(); - let msm = ecs.read_resource::(); - let time = ecs.read_resource::