Merge branch 'isse/event-systems' into 'master'

Seperate events to seperate event busses and use systems to handle them

See merge request veloren/veloren!4189
This commit is contained in:
Marcel 2024-02-08 09:13:33 +00:00
commit 3ac2729b99
66 changed files with 5521 additions and 4609 deletions

View File

@ -2140,12 +2140,7 @@ impl Client {
&self.connected_server_constants, &self.connected_server_constants,
|_, _| {}, |_, _| {},
); );
// TODO: avoid emitting these in the first place
let _ = self
.state
.ecs()
.fetch::<EventBus<common::event::ServerEvent>>()
.recv_all();
// TODO: avoid emitting these in the first place OR actually use outcomes // TODO: avoid emitting these in the first place OR actually use outcomes
// generated locally on the client (if they can be deduplicated from // generated locally on the client (if they can be deduplicated from
// ones that the server generates or if the client can reliably generate // ones that the server generates or if the client can reliably generate

View File

@ -75,7 +75,7 @@ slotmap = { version = "1.0", features = ["serde"] }
indexmap = { version = "1.9.3", features = ["rayon"] } indexmap = { version = "1.9.3", features = ["rayon"] }
# ECS # ECS
specs = { workspace = true, features = ["serde", "storage-event-control"] } specs = { workspace = true, features = ["serde", "storage-event-control", "shred-derive"] }
[dev-dependencies] [dev-dependencies]
#bench #bench

View File

@ -14,7 +14,10 @@ use crate::{
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, HealthChange, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, HealthChange,
Inventory, Ori, Player, Poise, PoiseChange, SkillSet, Stats, Inventory, Ori, Player, Poise, PoiseChange, SkillSet, Stats,
}, },
event::ServerEvent, event::{
BuffEvent, ComboChangeEvent, EmitExt, EnergyChangeEvent, EntityAttackedHookEvent,
HealthChangeEvent, KnockbackEvent, ParryHookEvent, PoiseChangeEvent,
},
outcome::Outcome, outcome::Outcome,
resources::{Secs, Time}, resources::{Secs, Time},
states::utils::StageSection, states::utils::StageSection,
@ -144,7 +147,7 @@ impl Attack {
dir: Dir, dir: Dir,
damage: Damage, damage: Damage,
msm: &MaterialStatManifest, msm: &MaterialStatManifest,
mut emit: impl FnMut(ServerEvent), emitters: &mut impl EmitExt<ParryHookEvent>,
mut emit_outcome: impl FnMut(Outcome), mut emit_outcome: impl FnMut(Outcome),
) -> f32 { ) -> f32 {
if damage.value > 0.0 { if damage.value > 0.0 {
@ -164,7 +167,7 @@ impl Attack {
pos: target.pos, pos: target.pos,
uid: target.uid, uid: target.uid,
}); });
emit(ServerEvent::ParryHook { emitters.emit(ParryHookEvent {
defender: target.entity, defender: target.entity,
attacker: attacker.map(|a| a.entity), attacker: attacker.map(|a| a.entity),
source, source,
@ -203,7 +206,16 @@ impl Attack {
strength_modifier: f32, strength_modifier: f32,
attack_source: AttackSource, attack_source: AttackSource,
time: Time, time: Time,
mut emit: impl FnMut(ServerEvent), emitters: &mut (
impl EmitExt<HealthChangeEvent>
+ EmitExt<EnergyChangeEvent>
+ EmitExt<ParryHookEvent>
+ EmitExt<KnockbackEvent>
+ EmitExt<BuffEvent>
+ EmitExt<PoiseChangeEvent>
+ EmitExt<ComboChangeEvent>
+ EmitExt<EntityAttackedHookEvent>
),
mut emit_outcome: impl FnMut(Outcome), mut emit_outcome: impl FnMut(Outcome),
rng: &mut rand::rngs::ThreadRng, rng: &mut rand::rngs::ThreadRng,
damage_instance_offset: u64, damage_instance_offset: u64,
@ -255,7 +267,7 @@ impl Attack {
dir, dir,
damage.damage, damage.damage,
msm, msm,
&mut emit, emitters,
&mut emit_outcome, &mut emit_outcome,
); );
let change = damage.damage.calculate_health_change( let change = damage.damage.calculate_health_change(
@ -271,7 +283,7 @@ impl Attack {
accumulated_damage += applied_damage; accumulated_damage += applied_damage;
if change.amount.abs() > Health::HEALTH_EPSILON { if change.amount.abs() > Health::HEALTH_EPSILON {
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -293,12 +305,12 @@ impl Attack {
precise: precision_mult.is_some(), precise: precision_mult.is_some(),
instance: damage_instance, instance: damage_instance,
}; };
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change: health_change, change: health_change,
}); });
} }
emit(ServerEvent::EnergyChange { emitters.emit(EnergyChangeEvent {
entity: target.entity, entity: target.entity,
change: -energy_change, change: -energy_change,
}); });
@ -344,12 +356,12 @@ impl Attack {
precise: precision_mult.is_some(), precise: precision_mult.is_some(),
time, time,
}; };
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change: health_change, change: health_change,
}); });
} else { } else {
emit(ServerEvent::PoiseChange { emitters.emit(PoiseChangeEvent {
entity: target.entity, entity: target.entity,
change: poise_change, change: poise_change,
}); });
@ -366,7 +378,7 @@ impl Attack {
let impulse = let impulse =
kb.calculate_impulse(dir, target.char_state) * strength_modifier; kb.calculate_impulse(dir, target.char_state) * strength_modifier;
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
emit(ServerEvent::Knockback { emitters.emit(KnockbackEvent {
entity: target.entity, entity: target.entity,
impulse, impulse,
}); });
@ -374,7 +386,7 @@ impl Attack {
}, },
CombatEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker) = attacker { if let Some(attacker) = attacker {
emit(ServerEvent::EnergyChange { emitters.emit(EnergyChangeEvent {
entity: attacker.entity, entity: attacker.entity,
change: *ec change: *ec
* compute_energy_reward_mod(attacker.inventory, msm) * compute_energy_reward_mod(attacker.inventory, msm)
@ -385,7 +397,7 @@ impl Attack {
}, },
CombatEffect::Buff(b) => { CombatEffect::Buff(b) => {
if rng.gen::<f32>() < b.chance { if rng.gen::<f32>() < b.chance {
emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity: target.entity, entity: target.entity,
buff_change: BuffChange::Add(b.to_buff( buff_change: BuffChange::Add(b.to_buff(
time, time,
@ -409,7 +421,7 @@ impl Attack {
instance: rand::random(), instance: rand::random(),
}; };
if change.amount.abs() > Health::HEALTH_EPSILON { if change.amount.abs() > Health::HEALTH_EPSILON {
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: attacker_entity, entity: attacker_entity,
change, change,
}); });
@ -435,7 +447,7 @@ impl Attack {
cause: Some(damage.damage.source), cause: Some(damage.damage.source),
time, time,
}; };
emit(ServerEvent::PoiseChange { emitters.emit(PoiseChangeEvent {
entity: target.entity, entity: target.entity,
change: poise_change, change: poise_change,
}); });
@ -451,7 +463,7 @@ impl Attack {
instance: rand::random(), instance: rand::random(),
}; };
if change.amount.abs() > Health::HEALTH_EPSILON { if change.amount.abs() > Health::HEALTH_EPSILON {
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -460,7 +472,7 @@ impl Attack {
CombatEffect::Combo(c) => { CombatEffect::Combo(c) => {
// Not affected by strength modifier as integer // Not affected by strength modifier as integer
if let Some(attacker_entity) = attacker.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::ComboChange { emitters.emit(ComboChangeEvent {
entity: attacker_entity, entity: attacker_entity,
change: *c, change: *c,
}); });
@ -476,7 +488,7 @@ impl Attack {
change.amount *= damage; change.amount *= damage;
change change
}; };
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -484,7 +496,7 @@ impl Attack {
}, },
CombatEffect::RefreshBuff(chance, b) => { CombatEffect::RefreshBuff(chance, b) => {
if rng.gen::<f32>() < *chance { if rng.gen::<f32>() < *chance {
emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity: target.entity, entity: target.entity,
buff_change: BuffChange::Refresh(*b), buff_change: BuffChange::Refresh(*b),
}); });
@ -497,7 +509,7 @@ impl Attack {
change.amount *= damage; change.amount *= damage;
change change
}; };
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -510,7 +522,7 @@ impl Attack {
change.amount *= damage; change.amount *= damage;
change change
}; };
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -543,7 +555,7 @@ impl Attack {
{ {
let sufficient_energy = e.current() >= *r; let sufficient_energy = e.current() >= *r;
if sufficient_energy { if sufficient_energy {
emit(ServerEvent::EnergyChange { emitters.emit(EnergyChangeEvent {
entity, entity,
change: -*r, change: -*r,
}); });
@ -563,7 +575,7 @@ impl Attack {
{ {
let sufficient_combo = c.counter() >= *r; let sufficient_combo = c.counter() >= *r;
if sufficient_combo { if sufficient_combo {
emit(ServerEvent::ComboChange { emitters.emit(ComboChangeEvent {
entity, entity,
change: -(*r as i32), change: -(*r as i32),
}); });
@ -585,7 +597,7 @@ impl Attack {
let impulse = let impulse =
kb.calculate_impulse(dir, target.char_state) * strength_modifier; kb.calculate_impulse(dir, target.char_state) * strength_modifier;
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
emit(ServerEvent::Knockback { emitters.emit(KnockbackEvent {
entity: target.entity, entity: target.entity,
impulse, impulse,
}); });
@ -593,7 +605,7 @@ impl Attack {
}, },
CombatEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker) = attacker { if let Some(attacker) = attacker {
emit(ServerEvent::EnergyChange { emitters.emit(EnergyChangeEvent {
entity: attacker.entity, entity: attacker.entity,
change: ec change: ec
* compute_energy_reward_mod(attacker.inventory, msm) * compute_energy_reward_mod(attacker.inventory, msm)
@ -604,7 +616,7 @@ impl Attack {
}, },
CombatEffect::Buff(b) => { CombatEffect::Buff(b) => {
if rng.gen::<f32>() < b.chance { if rng.gen::<f32>() < b.chance {
emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity: target.entity, entity: target.entity,
buff_change: BuffChange::Add(b.to_buff( buff_change: BuffChange::Add(b.to_buff(
time, time,
@ -628,7 +640,7 @@ impl Attack {
instance: rand::random(), instance: rand::random(),
}; };
if change.amount.abs() > Health::HEALTH_EPSILON { if change.amount.abs() > Health::HEALTH_EPSILON {
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: attacker_entity, entity: attacker_entity,
change, change,
}); });
@ -654,7 +666,7 @@ impl Attack {
cause: Some(attack_source.into()), cause: Some(attack_source.into()),
time, time,
}; };
emit(ServerEvent::PoiseChange { emitters.emit(PoiseChangeEvent {
entity: target.entity, entity: target.entity,
change: poise_change, change: poise_change,
}); });
@ -670,7 +682,7 @@ impl Attack {
instance: rand::random(), instance: rand::random(),
}; };
if change.amount.abs() > Health::HEALTH_EPSILON { if change.amount.abs() > Health::HEALTH_EPSILON {
emit(ServerEvent::HealthChange { emitters.emit(HealthChangeEvent {
entity: target.entity, entity: target.entity,
change, change,
}); });
@ -679,7 +691,7 @@ impl Attack {
CombatEffect::Combo(c) => { CombatEffect::Combo(c) => {
// Not affected by strength modifier as integer // Not affected by strength modifier as integer
if let Some(attacker_entity) = attacker.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::ComboChange { emitters.emit(ComboChangeEvent {
entity: attacker_entity, entity: attacker_entity,
change: c, change: c,
}); });
@ -689,7 +701,7 @@ impl Attack {
CombatEffect::StageVulnerable(_, _) => {}, CombatEffect::StageVulnerable(_, _) => {},
CombatEffect::RefreshBuff(chance, b) => { CombatEffect::RefreshBuff(chance, b) => {
if rng.gen::<f32>() < chance { if rng.gen::<f32>() < chance {
emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity: target.entity, entity: target.entity,
buff_change: BuffChange::Refresh(b), buff_change: BuffChange::Refresh(b),
}); });
@ -704,7 +716,7 @@ impl Attack {
// Emits event to handle things that should happen for any successful 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 // regardless of if the attack had any damages or effects in it
if is_applied { if is_applied {
emit(ServerEvent::EntityAttackedHook { emitters.emit(EntityAttackedHookEvent {
entity: target.entity, entity: target.entity,
attacker: attacker.map(|a| a.entity), attacker: attacker.map(|a| a.entity),
}); });

View File

@ -4,7 +4,8 @@ use crate::{
ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction,
Density, Energy, InputAttr, InputKind, Ori, Pos, Vel, Density, Energy, InputAttr, InputKind, Ori, Pos, Vel,
}, },
event::{LocalEvent, ServerEvent}, event::{self, EmitExt, LocalEvent},
event_emitters,
resources::Time, resources::Time,
states::{ states::{
self, self,
@ -34,19 +35,46 @@ pub struct StateUpdate {
pub character_activity: CharacterActivity, pub character_activity: CharacterActivity,
} }
pub struct OutputEvents<'a> { event_emitters! {
local: &'a mut Vec<LocalEvent>, pub struct CharacterStateEvents[CharacterStateEventEmitters] {
server: &'a mut Vec<ServerEvent>, 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 struct OutputEvents<'a, 'b> {
pub fn new(local: &'a mut Vec<LocalEvent>, server: &'a mut Vec<ServerEvent>) -> Self { local: &'a mut Vec<LocalEvent>,
server: &'a mut CharacterStateEventEmitters<'b>,
}
impl<'a, 'b: 'a> OutputEvents<'a, 'b> {
pub fn new(
local: &'a mut Vec<LocalEvent>,
server: &'a mut CharacterStateEventEmitters<'b>,
) -> Self {
Self { local, server } Self { local, server }
} }
pub fn emit_local(&mut self, event: LocalEvent) { self.local.push(event); } 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<E>(&mut self, event: E)
where
CharacterStateEventEmitters<'b>: EmitExt<E>,
{
self.server.emit(event);
}
} }
impl From<&JoinData<'_>> for StateUpdate { impl From<&JoinData<'_>> for StateUpdate {

View File

@ -217,6 +217,13 @@ impl<G> GenericChatMsg<G> {
Self { chat_type, content } 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<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> { pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
let chat_type = match self.chat_type { let chat_type = match self.chat_type {
ChatType::Online(a) => ChatType::Online(a), ChatType::Online(a) => ChatType::Online(a),

View File

@ -2,7 +2,7 @@ use crate::{comp::Alignment, uid::Uid};
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slab::Slab; use slab::Slab;
use specs::{Component, DerefFlaggedStorage, Join, LendJoin}; use specs::{storage::GenericReadStorage, Component, DerefFlaggedStorage, Join, LendJoin};
use tracing::{error, warn}; use tracing::{error, warn};
// Primitive group system // Primitive group system
@ -82,7 +82,6 @@ impl<E> ChangeNotification<E> {
} }
type GroupsMut<'a> = specs::WriteStorage<'a, Group>; type GroupsMut<'a> = specs::WriteStorage<'a, Group>;
type Groups<'a> = specs::ReadStorage<'a, Group>;
type Alignments<'a> = specs::ReadStorage<'a, Alignment>; type Alignments<'a> = specs::ReadStorage<'a, Alignment>;
type Uids<'a> = specs::ReadStorage<'a, Uid>; type Uids<'a> = specs::ReadStorage<'a, Uid>;
@ -98,7 +97,7 @@ fn pets(
entity: specs::Entity, entity: specs::Entity,
uid: Uid, uid: Uid,
alignments: &Alignments, alignments: &Alignments,
entities: &specs::Entities, entities: &specs::world::EntitiesRes,
) -> Vec<specs::Entity> { ) -> Vec<specs::Entity> {
(entities, alignments) (entities, alignments)
.join() .join()
@ -112,7 +111,7 @@ fn pets(
pub fn members<'a>( pub fn members<'a>(
group: Group, group: Group,
groups: impl Join<Type = &'a Group> + 'a, groups: impl Join<Type = &'a Group> + 'a,
entities: &'a specs::Entities, entities: &'a specs::world::EntitiesRes,
alignments: &'a Alignments, alignments: &'a Alignments,
uids: &'a Uids, uids: &'a Uids,
) -> impl Iterator<Item = (specs::Entity, Role)> + 'a { ) -> impl Iterator<Item = (specs::Entity, Role)> + 'a {
@ -331,7 +330,7 @@ impl GroupManager {
groups: &mut GroupsMut, groups: &mut GroupsMut,
alignments: &Alignments, alignments: &Alignments,
uids: &Uids, uids: &Uids,
entities: &specs::Entities, entities: &specs::world::EntitiesRes,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>), notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) { ) {
self.remove_from_group(member, groups, alignments, uids, entities, notifier, true); self.remove_from_group(member, groups, alignments, uids, entities, notifier, true);
@ -346,7 +345,7 @@ impl GroupManager {
groups: &mut GroupsMut, groups: &mut GroupsMut,
alignments: &Alignments, alignments: &Alignments,
uids: &Uids, uids: &Uids,
entities: &specs::Entities, entities: &specs::world::EntitiesRes,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>), notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
to_be_deleted: bool, to_be_deleted: bool,
) { ) {
@ -493,13 +492,13 @@ impl GroupManager {
// Assign new group leader // Assign new group leader
// Does nothing if new leader is not part of a group // Does nothing if new leader is not part of a group
pub fn assign_leader( pub fn assign_leader<'a>(
&mut self, &mut self,
new_leader: specs::Entity, new_leader: specs::Entity,
groups: &Groups, groups: impl GenericReadStorage<Component = Group> + Join<Type = &'a Group> + 'a,
entities: &specs::Entities, entities: &'a specs::Entities,
alignments: &Alignments, alignments: &'a Alignments,
uids: &Uids, uids: &'a Uids,
mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>), mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) { ) {
let group = match groups.get(new_leader) { let group = match groups.get(new_leader) {

View File

@ -7,7 +7,7 @@ use crate::{
dialogue::Subject, dialogue::Subject,
invite::{InviteKind, InviteResponse}, invite::{InviteKind, InviteResponse},
misc::PortalData, misc::PortalData,
DisconnectReason, Ori, Pos, DisconnectReason, LootOwner, Ori, Pos, UnresolvedChatMsg, Vel,
}, },
lottery::LootSpec, lottery::LootSpec,
mounting::VolumePos, mounting::VolumePos,
@ -20,7 +20,7 @@ use crate::{
Explosion, Explosion,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::Entity as EcsEntity; use specs::{Entity as EcsEntity, World};
use std::{collections::VecDeque, ops::DerefMut, sync::Mutex}; use std::{collections::VecDeque, ops::DerefMut, sync::Mutex};
use uuid::Uuid; use uuid::Uuid;
use vek::*; use vek::*;
@ -132,217 +132,281 @@ impl NpcBuilder {
} }
} }
#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587 pub struct ClientConnectedEvent {
#[derive(strum::EnumDiscriminants)] pub entity: EcsEntity,
#[strum_discriminants(repr(usize))] }
#[strum_discriminants(derive(strum::EnumVariantNames))] pub struct ClientDisconnectEvent(pub EcsEntity, pub DisconnectReason);
pub enum ServerEvent { pub struct ClientDisconnectWithoutPersistenceEvent(pub EcsEntity);
Explosion {
pos: Vec3<f32>, pub struct ChatEvent(pub UnresolvedChatMsg);
explosion: Explosion, pub struct CommandEvent(pub EcsEntity, pub String, pub Vec<String>);
owner: Option<Uid>,
}, // Entity Creation
Bonk { pub struct CreateWaypointEvent(pub Vec3<f32>);
pos: Vec3<f32>, pub struct CreateTeleporterEvent(pub Vec3<f32>, pub PortalData);
owner: Option<Uid>,
target: Option<Uid>, pub struct CreateNpcEvent {
}, pub pos: Pos,
HealthChange { pub ori: Ori,
entity: EcsEntity, pub npc: NpcBuilder,
change: comp::HealthChange, pub rider: Option<NpcBuilder>,
}, }
PoiseChange {
entity: EcsEntity, pub struct CreateShipEvent {
change: comp::PoiseChange, pub pos: Pos,
}, pub ori: Ori,
Delete(EcsEntity), pub ship: comp::ship::Body,
Destroy { pub rtsim_entity: Option<RtSimEntity>,
entity: EcsEntity, pub driver: Option<NpcBuilder>,
cause: comp::HealthChange, }
},
InventoryManip(EcsEntity, comp::InventoryManip), pub struct CreateItemDropEvent {
GroupManip(EcsEntity, comp::GroupManip), pub pos: Pos,
Respawn(EcsEntity), pub vel: Vel,
Shoot { pub ori: Ori,
entity: EcsEntity, pub item: comp::Item,
pos: Pos, pub loot_owner: Option<LootOwner>,
dir: Dir, }
body: comp::Body, pub struct CreateObjectEvent {
light: Option<comp::LightEmitter>, pub pos: Pos,
projectile: comp::Projectile, pub vel: Vel,
speed: f32, pub body: comp::object::Body,
object: Option<comp::Object>, pub object: Option<comp::Object>,
}, pub item: Option<comp::Item>,
Shockwave { pub light_emitter: Option<comp::LightEmitter>,
properties: comp::shockwave::Properties, pub stats: Option<comp::Stats>,
pos: Pos, }
ori: Ori,
}, pub struct ExplosionEvent {
Knockback { pub pos: Vec3<f32>,
entity: EcsEntity, pub explosion: Explosion,
impulse: Vec3<f32>, pub owner: Option<Uid>,
}, }
LandOnGround {
entity: EcsEntity, pub struct BonkEvent {
vel: Vec3<f32>, pub pos: Vec3<f32>,
surface_normal: Vec3<f32>, pub owner: Option<Uid>,
}, pub target: Option<Uid>,
EnableLantern(EcsEntity), }
DisableLantern(EcsEntity),
NpcInteract(EcsEntity, EcsEntity, Subject), pub struct HealthChangeEvent {
InviteResponse(EcsEntity, InviteResponse), pub entity: EcsEntity,
InitiateInvite(EcsEntity, Uid, InviteKind), pub change: comp::HealthChange,
ProcessTradeAction(EcsEntity, TradeId, TradeAction), }
Mount(EcsEntity, EcsEntity),
MountVolume(EcsEntity, VolumePos), pub struct PoiseChangeEvent {
Unmount(EcsEntity), pub entity: EcsEntity,
SetPetStay(EcsEntity, EcsEntity, bool), pub change: comp::PoiseChange,
Possess(Uid, Uid), }
/// Inserts default components for a character when loading into the game
InitCharacterData { pub struct DeleteEvent(pub EcsEntity);
entity: EcsEntity,
character_id: CharacterId, pub struct DestroyEvent {
requested_view_distances: crate::ViewDistances, pub entity: EcsEntity,
}, pub cause: comp::HealthChange,
InitSpectator(EcsEntity, crate::ViewDistances), }
UpdateCharacterData {
entity: EcsEntity, pub struct InventoryManipEvent(pub EcsEntity, pub comp::InventoryManip);
components: (
comp::Body, pub struct GroupManipEvent(pub EcsEntity, pub comp::GroupManip);
comp::Stats,
comp::SkillSet, pub struct RespawnEvent(pub EcsEntity);
comp::Inventory,
Option<comp::Waypoint>, pub struct ShootEvent {
Vec<(comp::Pet, comp::Body, comp::Stats)>, pub entity: EcsEntity,
comp::ActiveAbilities, pub pos: Pos,
Option<comp::MapMarker>, pub dir: Dir,
), pub body: comp::Body,
metadata: UpdateCharacterMetadata, pub light: Option<comp::LightEmitter>,
}, pub projectile: comp::Projectile,
ExitIngame { pub speed: f32,
entity: EcsEntity, pub object: Option<comp::Object>,
}, }
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
CreateNpc { pub struct ShockwaveEvent {
pos: Pos, pub properties: comp::shockwave::Properties,
ori: Ori, pub pos: Pos,
npc: NpcBuilder, pub ori: Ori,
rider: Option<NpcBuilder>, }
},
CreateShip { pub struct KnockbackEvent {
pos: Pos, pub entity: EcsEntity,
ori: Ori, pub impulse: Vec3<f32>,
ship: comp::ship::Body, }
rtsim_entity: Option<RtSimEntity>,
driver: Option<NpcBuilder>, pub struct LandOnGroundEvent {
}, pub entity: EcsEntity,
CreateWaypoint(Vec3<f32>), pub vel: Vec3<f32>,
CreateTeleporter(Vec3<f32>, PortalData), pub surface_normal: Vec3<f32>,
ClientDisconnect(EcsEntity, DisconnectReason), }
ClientDisconnectWithoutPersistence(EcsEntity),
Command(EcsEntity, String, Vec<String>), pub struct SetLanternEvent(pub EcsEntity, pub bool);
/// Send a chat message to the player from an npc or other player
Chat(comp::UnresolvedChatMsg), pub struct NpcInteractEvent(pub EcsEntity, pub EcsEntity, pub Subject);
Aura {
entity: EcsEntity, pub struct InviteResponseEvent(pub EcsEntity, pub InviteResponse);
aura_change: comp::AuraChange,
}, pub struct InitiateInviteEvent(pub EcsEntity, pub Uid, pub InviteKind);
Buff {
entity: EcsEntity, pub struct ProcessTradeActionEvent(pub EcsEntity, pub TradeId, pub TradeAction);
buff_change: comp::BuffChange,
}, pub struct MountEvent(pub EcsEntity, pub EcsEntity);
EnergyChange {
entity: EcsEntity, pub struct MountVolumeEvent(pub EcsEntity, pub VolumePos);
change: f32,
}, pub struct UnmountEvent(pub EcsEntity);
ComboChange {
entity: EcsEntity, pub struct SetPetStayEvent(pub EcsEntity, pub EcsEntity, pub bool);
change: i32,
}, pub struct PossessEvent(pub Uid, pub Uid);
ParryHook {
defender: EcsEntity, pub struct InitializeCharacterEvent {
attacker: Option<EcsEntity>, pub entity: EcsEntity,
source: AttackSource, pub character_id: CharacterId,
}, pub requested_view_distances: crate::ViewDistances,
RequestSiteInfo { }
entity: EcsEntity,
id: SiteId, pub struct InitializeSpectatorEvent(pub EcsEntity, pub crate::ViewDistances);
},
// Attempt to mine a block, turning it into an item pub struct UpdateCharacterDataEvent {
MineBlock { pub entity: EcsEntity,
entity: EcsEntity, pub components: (
pos: Vec3<i32>, comp::Body,
tool: Option<comp::tool::ToolKind>, comp::Stats,
}, comp::SkillSet,
TeleportTo { comp::Inventory,
entity: EcsEntity, Option<comp::Waypoint>,
target: Uid, Vec<(comp::Pet, comp::Body, comp::Stats)>,
max_range: Option<f32>, comp::ActiveAbilities,
}, Option<comp::MapMarker>,
CreateSafezone { ),
range: Option<f32>, pub metadata: UpdateCharacterMetadata,
pos: Pos, }
},
Sound { pub struct ExitIngameEvent {
sound: Sound, pub entity: EcsEntity,
}, }
CreateSprite {
pos: Vec3<i32>, pub struct AuraEvent {
sprite: SpriteKind, pub entity: EcsEntity,
del_timeout: Option<(f32, f32)>, pub aura_change: comp::AuraChange,
}, }
TamePet {
pet_entity: EcsEntity, pub struct BuffEvent {
owner_entity: EcsEntity, pub entity: EcsEntity,
}, pub buff_change: comp::BuffChange,
EntityAttackedHook { }
entity: EcsEntity,
attacker: Option<EcsEntity>, pub struct EnergyChangeEvent {
}, pub entity: EcsEntity,
ChangeAbility { pub change: f32,
entity: EcsEntity, }
slot: usize,
auxiliary_key: comp::ability::AuxiliaryKey, pub struct ComboChangeEvent {
new_ability: comp::ability::AuxiliaryAbility, pub entity: EcsEntity,
}, pub change: i32,
UpdateMapMarker { }
entity: EcsEntity,
update: comp::MapMarkerChange, pub struct ParryHookEvent {
}, pub defender: EcsEntity,
MakeAdmin { pub attacker: Option<EcsEntity>,
entity: EcsEntity, pub source: AttackSource,
admin: comp::Admin, }
uuid: Uuid,
}, pub struct RequestSiteInfoEvent {
DeleteCharacter { pub entity: EcsEntity,
entity: EcsEntity, pub id: SiteId,
requesting_player_uuid: String, }
character_id: CharacterId,
}, // Attempt to mine a block, turning it into an item
ChangeStance { pub struct MineBlockEvent {
entity: EcsEntity, pub entity: EcsEntity,
stance: comp::Stance, pub pos: Vec3<i32>,
}, pub tool: Option<comp::tool::ToolKind>,
ChangeBody { }
entity: EcsEntity,
new_body: comp::Body, pub struct TeleportToEvent {
}, pub entity: EcsEntity,
RemoveLightEmitter { pub target: Uid,
entity: EcsEntity, pub max_range: Option<f32>,
}, }
TeleportToPosition {
entity: EcsEntity, pub struct CreateSafezoneEvent {
position: Vec3<f32>, pub range: Option<f32>,
}, pub pos: Pos,
StartTeleporting { }
entity: EcsEntity,
portal: EcsEntity, pub struct SoundEvent {
}, pub sound: Sound,
ToggleSpriteLight { }
entity: EcsEntity,
pos: Vec3<i32>, pub struct CreateSpriteEvent {
enable: bool, pub pos: Vec3<i32>,
}, 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<EcsEntity>,
}
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<f32>,
}
pub struct StartTeleportingEvent {
pub entity: EcsEntity,
pub portal: EcsEntity,
}
pub struct ToggleSpriteLightEvent {
pub entity: EcsEntity,
pub pos: Vec3<i32>,
pub enable: bool,
} }
pub struct EventBus<E> { pub struct EventBus<E> {
@ -370,11 +434,15 @@ impl<E> EventBus<E> {
pub fn recv_all(&self) -> impl ExactSizeIterator<Item = E> { pub fn recv_all(&self) -> impl ExactSizeIterator<Item = E> {
std::mem::take(self.queue.lock().unwrap().deref_mut()).into_iter() std::mem::take(self.queue.lock().unwrap().deref_mut()).into_iter()
} }
pub fn recv_all_mut(&mut self) -> impl ExactSizeIterator<Item = E> {
std::mem::take(self.queue.get_mut().unwrap()).into_iter()
}
} }
pub struct Emitter<'a, E> { pub struct Emitter<'a, E> {
bus: &'a EventBus<E>, bus: &'a EventBus<E>,
events: VecDeque<E>, pub events: VecDeque<E>,
} }
impl<'a, E> Emitter<'a, E> { impl<'a, E> Emitter<'a, E> {
@ -395,3 +463,137 @@ impl<'a, E> Drop for Emitter<'a, E> {
} }
} }
} }
pub trait EmitExt<E> {
fn emit(&mut self, event: E);
fn emit_many(&mut self, events: impl IntoIterator<Item = E>);
}
pub fn register_event_busses(ecs: &mut World) {
ecs.insert(EventBus::<ClientConnectedEvent>::default());
ecs.insert(EventBus::<ClientDisconnectEvent>::default());
ecs.insert(EventBus::<ClientDisconnectWithoutPersistenceEvent>::default());
ecs.insert(EventBus::<ChatEvent>::default());
ecs.insert(EventBus::<CommandEvent>::default());
ecs.insert(EventBus::<CreateWaypointEvent>::default());
ecs.insert(EventBus::<CreateTeleporterEvent>::default());
ecs.insert(EventBus::<CreateNpcEvent>::default());
ecs.insert(EventBus::<CreateShipEvent>::default());
ecs.insert(EventBus::<CreateItemDropEvent>::default());
ecs.insert(EventBus::<CreateObjectEvent>::default());
ecs.insert(EventBus::<ExplosionEvent>::default());
ecs.insert(EventBus::<BonkEvent>::default());
ecs.insert(EventBus::<HealthChangeEvent>::default());
ecs.insert(EventBus::<PoiseChangeEvent>::default());
ecs.insert(EventBus::<DeleteEvent>::default());
ecs.insert(EventBus::<DestroyEvent>::default());
ecs.insert(EventBus::<InventoryManipEvent>::default());
ecs.insert(EventBus::<GroupManipEvent>::default());
ecs.insert(EventBus::<RespawnEvent>::default());
ecs.insert(EventBus::<ShootEvent>::default());
ecs.insert(EventBus::<ShockwaveEvent>::default());
ecs.insert(EventBus::<KnockbackEvent>::default());
ecs.insert(EventBus::<LandOnGroundEvent>::default());
ecs.insert(EventBus::<SetLanternEvent>::default());
ecs.insert(EventBus::<NpcInteractEvent>::default());
ecs.insert(EventBus::<InviteResponseEvent>::default());
ecs.insert(EventBus::<InitiateInviteEvent>::default());
ecs.insert(EventBus::<ProcessTradeActionEvent>::default());
ecs.insert(EventBus::<MountEvent>::default());
ecs.insert(EventBus::<MountVolumeEvent>::default());
ecs.insert(EventBus::<UnmountEvent>::default());
ecs.insert(EventBus::<SetPetStayEvent>::default());
ecs.insert(EventBus::<PossessEvent>::default());
ecs.insert(EventBus::<InitializeCharacterEvent>::default());
ecs.insert(EventBus::<InitializeSpectatorEvent>::default());
ecs.insert(EventBus::<UpdateCharacterDataEvent>::default());
ecs.insert(EventBus::<ExitIngameEvent>::default());
ecs.insert(EventBus::<AuraEvent>::default());
ecs.insert(EventBus::<BuffEvent>::default());
ecs.insert(EventBus::<EnergyChangeEvent>::default());
ecs.insert(EventBus::<ComboChangeEvent>::default());
ecs.insert(EventBus::<ParryHookEvent>::default());
ecs.insert(EventBus::<RequestSiteInfoEvent>::default());
ecs.insert(EventBus::<MineBlockEvent>::default());
ecs.insert(EventBus::<TeleportToEvent>::default());
ecs.insert(EventBus::<CreateSafezoneEvent>::default());
ecs.insert(EventBus::<SoundEvent>::default());
ecs.insert(EventBus::<CreateSpriteEvent>::default());
ecs.insert(EventBus::<TamePetEvent>::default());
ecs.insert(EventBus::<EntityAttackedHookEvent>::default());
ecs.insert(EventBus::<ChangeAbilityEvent>::default());
ecs.insert(EventBus::<UpdateMapMarkerEvent>::default());
ecs.insert(EventBus::<MakeAdminEvent>::default());
ecs.insert(EventBus::<DeleteCharacterEvent>::default());
ecs.insert(EventBus::<ChangeStanceEvent>::default());
ecs.insert(EventBus::<ChangeBodyEvent>::default());
ecs.insert(EventBus::<RemoveLightEmitterEvent>::default());
ecs.insert(EventBus::<TeleportToPositionEvent>::default());
ecs.insert(EventBus::<StartTeleportingEvent>::default());
ecs.insert(EventBus::<ToggleSpriteLightEvent>::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<specs::Read<'a, $crate::event::EventBus<$ty>>>),+
}
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<Item = $ty>) { self.$ev_ident.as_mut().map(|e| e.emit_many(events)); }
}
)+
)+
}
$(
$vis use event_emitters::{$read_data, $emitters};
)+
}
}

View File

@ -5,7 +5,7 @@ use crate::{
character_state::OutputEvents, character_state::OutputEvents,
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::ServerEvent, event::{AuraEvent, ComboChangeEvent},
resources::Secs, resources::Secs,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -95,12 +95,12 @@ impl CharacterBehavior for Data {
(self.static_data.combo_at_cast.max(1) as f32).sqrt(); (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, entity: data.entity,
change: -(self.static_data.combo_at_cast as i32), change: -(self.static_data.combo_at_cast as i32),
}); });
} }
output_events.emit_server(ServerEvent::Aura { output_events.emit_server(AuraEvent {
entity: data.entity, entity: data.entity,
aura_change: AuraChange::Add(aura), aura_change: AuraChange::Add(aura),
}); });

View File

@ -5,7 +5,7 @@ use crate::{
object::Body::{GrenadeClay, LaserBeam}, object::Body::{GrenadeClay, LaserBeam},
Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate, Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ShootEvent},
outcome::Outcome, outcome::Outcome,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -127,7 +127,7 @@ impl CharacterBehavior for Data {
})) }))
.unwrap_or(data.inputs.look_dir); .unwrap_or(data.inputs.look_dir);
// Tells server to create and shoot the projectile // Tells server to create and shoot the projectile
output_events.emit_server(ServerEvent::Shoot { output_events.emit_server(ShootEvent {
entity: data.entity, entity: data.entity,
pos, pos,
dir, dir,

View File

@ -9,7 +9,7 @@ use crate::{
Body::Object, Body::Object,
CharacterState, Projectile, StateUpdate, CharacterState, Projectile, StateUpdate,
}, },
event::{LocalEvent, NpcBuilder, ServerEvent}, event::{CreateNpcEvent, LocalEvent, NpcBuilder},
npc::NPC_NAMES, npc::NPC_NAMES,
outcome::Outcome, outcome::Outcome,
skillset_builder::{self, SkillSetBuilder}, skillset_builder::{self, SkillSetBuilder},
@ -197,7 +197,7 @@ impl CharacterBehavior for Data {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Send server event to create npc // Send server event to create npc
output_events.emit_server(ServerEvent::CreateNpc { output_events.emit_server(CreateNpcEvent {
pos: comp::Pos( pos: comp::Pos(
collision_vector - Vec3::unit_z() * obstacle_z + extra_height, collision_vector - Vec3::unit_z() * obstacle_z + extra_height,
), ),

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, StateUpdate},
event::ServerEvent, event::TeleportToEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -52,7 +52,7 @@ impl CharacterBehavior for Data {
// provided // provided
if let Some(input_attr) = self.static_data.ability_info.input_attr { if let Some(input_attr) = self.static_data.ability_info.input_attr {
if let Some(target) = input_attr.target_entity { if let Some(target) = input_attr.target_entity {
output_events.emit_server(ServerEvent::TeleportTo { output_events.emit_server(TeleportToEvent {
entity: data.entity, entity: data.entity,
target, target,
max_range: Some(self.static_data.max_range), max_range: Some(self.static_data.max_range),

View File

@ -4,7 +4,7 @@ use crate::{
character_state::OutputEvents, projectile::ProjectileConstructor, Body, CharacterState, character_state::OutputEvents, projectile::ProjectileConstructor, Body, CharacterState,
LightEmitter, Pos, StateUpdate, LightEmitter, Pos, StateUpdate,
}, },
event::ServerEvent, event::ShootEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -123,7 +123,7 @@ impl CharacterBehavior for Data {
tool_stats, tool_stats,
self.static_data.damage_effect, self.static_data.damage_effect,
); );
output_events.emit_server(ServerEvent::Shoot { output_events.emit_server(ShootEvent {
entity: data.entity, entity: data.entity,
pos, pos,
dir: data.inputs.look_dir, dir: data.inputs.look_dir,

View File

@ -9,7 +9,7 @@ use crate::{
shockwave::{self, ShockwaveDodgeable}, shockwave::{self, ShockwaveDodgeable},
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{ExplosionEvent, LocalEvent, ShockwaveEvent},
outcome::Outcome, outcome::Outcome,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -182,7 +182,7 @@ impl CharacterBehavior for Data {
owner: Some(*data.uid), owner: Some(*data.uid),
specifier: self.static_data.specifier, specifier: self.static_data.specifier,
}; };
output_events.emit_server(ServerEvent::Shockwave { output_events.emit_server(ShockwaveEvent {
properties, properties,
pos: *data.pos, pos: *data.pos,
ori: *data.ori, ori: *data.ori,
@ -214,7 +214,7 @@ impl CharacterBehavior for Data {
reagent: Some(Reagent::White), reagent: Some(Reagent::White),
min_falloff: 0.5, min_falloff: 0.5,
}; };
output_events.emit_server(ServerEvent::Explosion { output_events.emit_server(ExplosionEvent {
pos: data.pos.0, pos: data.pos.0,
explosion, explosion,
owner: Some(*data.uid), owner: Some(*data.uid),

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat, combat,
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
event::ServerEvent, event::ComboChangeEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -117,7 +117,7 @@ impl CharacterBehavior for Data {
// Consume combo if any was required // Consume combo if any was required
if self.static_data.minimum_combo > 0 { if self.static_data.minimum_combo > 0 {
output_events.emit_server(ServerEvent::ComboChange { output_events.emit_server(ComboChangeEvent {
entity: data.entity, entity: data.entity,
change: -data.combo.map_or(0, |c| c.counter() as i32), change: -data.combo.map_or(0, |c| c.counter() as i32),
}); });

View File

@ -4,7 +4,7 @@ use crate::{
character_state::OutputEvents, Body, CharacterState, LightEmitter, Pos, character_state::OutputEvents, Body, CharacterState, LightEmitter, Pos,
ProjectileConstructor, StateUpdate, ProjectileConstructor, StateUpdate,
}, },
event::ServerEvent, event::{EnergyChangeEvent, ShootEvent},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *}, utils::{StageSection, *},
@ -144,7 +144,7 @@ impl CharacterBehavior for Data {
tool_stats, tool_stats,
self.static_data.damage_effect, self.static_data.damage_effect,
); );
output_events.emit_server(ServerEvent::Shoot { output_events.emit_server(ShootEvent {
entity: data.entity, entity: data.entity,
pos, pos,
dir: direction, dir: direction,
@ -156,7 +156,7 @@ impl CharacterBehavior for Data {
}); });
// Removes energy from character when arrow is fired // Removes energy from character when arrow is fired
output_events.emit_server(ServerEvent::EnergyChange { output_events.emit_server(EnergyChangeEvent {
entity: data.entity, entity: data.entity,
change: -self.static_data.energy_cost, change: -self.static_data.energy_cost,
}); });

View File

@ -4,7 +4,7 @@ use crate::{
character_state::{AttackFilters, OutputEvents}, character_state::{AttackFilters, OutputEvents},
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::ServerEvent, event::BuffEvent,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -69,7 +69,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Remove burning effect if active // Remove burning effect if active
output_events.emit_server(ServerEvent::Buff { output_events.emit_server(BuffEvent {
entity: data.entity, entity: data.entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Burning), buff_change: BuffChange::RemoveByKind(BuffKind::Burning),
}); });

View File

@ -4,7 +4,7 @@ use crate::{
character_state::OutputEvents, character_state::OutputEvents,
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{BuffEvent, ComboChangeEvent, LocalEvent},
outcome::Outcome, outcome::Outcome,
resources::Secs, resources::Secs,
states::{ states::{
@ -79,7 +79,7 @@ impl CharacterBehavior for Data {
} else { } else {
self.static_data.combo_cost self.static_data.combo_cost
}; };
output_events.emit_server(ServerEvent::ComboChange { output_events.emit_server(ComboChangeEvent {
entity: data.entity, entity: data.entity,
change: -(combo_consumption as i32), change: -(combo_consumption as i32),
}); });
@ -106,7 +106,7 @@ impl CharacterBehavior for Data {
if self.static_data.enforced_limit { if self.static_data.enforced_limit {
buff_cat_ids.push(BuffCategory::SelfBuff); buff_cat_ids.push(BuffCategory::SelfBuff);
output_events.emit_server(ServerEvent::Buff { output_events.emit_server(BuffEvent {
entity: data.entity, entity: data.entity,
buff_change: BuffChange::RemoveByCategory { buff_change: BuffChange::RemoveByCategory {
all_required: vec![BuffCategory::SelfBuff], all_required: vec![BuffCategory::SelfBuff],
@ -128,7 +128,7 @@ impl CharacterBehavior for Data {
*data.time, *data.time,
Some(data.stats), Some(data.stats),
); );
output_events.emit_server(ServerEvent::Buff { output_events.emit_server(BuffEvent {
entity: data.entity, entity: data.entity,
buff_change: BuffChange::Add(buff), buff_change: BuffChange::Add(buff),
}); });

View File

@ -8,7 +8,7 @@ use crate::{
shockwave::{self, ShockwaveDodgeable}, shockwave::{self, ShockwaveDodgeable},
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ShockwaveEvent},
outcome::Outcome, outcome::Outcome,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -124,7 +124,7 @@ impl CharacterBehavior for Data {
owner: Some(*data.uid), owner: Some(*data.uid),
specifier: self.static_data.specifier, specifier: self.static_data.specifier,
}; };
output_events.emit_server(ServerEvent::Shockwave { output_events.emit_server(ShockwaveEvent {
properties, properties,
pos: *data.pos, pos: *data.pos,
ori: *data.ori, ori: *data.ori,

View File

@ -4,7 +4,7 @@ use crate::{
character_state::OutputEvents, controller::InputKind, item::ItemDefinitionIdOwned, character_state::OutputEvents, controller::InputKind, item::ItemDefinitionIdOwned,
slot::InvSlotId, CharacterState, InventoryManip, StateUpdate, slot::InvSlotId, CharacterState, InventoryManip, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{InventoryManipEvent, LocalEvent, ToggleSpriteLightEvent},
outcome::Outcome, outcome::Outcome,
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
terrain::SpriteKind, terrain::SpriteKind,
@ -117,17 +117,16 @@ impl CharacterBehavior for Data {
sprite_pos: self.static_data.sprite_pos, sprite_pos: self.static_data.sprite_pos,
required_item: inv_slot, required_item: inv_slot,
}; };
match self.static_data.sprite_kind { match self.static_data.sprite_kind {
SpriteInteractKind::ToggleLight(enable) => { SpriteInteractKind::ToggleLight(enable) => {
output_events.emit_server(ServerEvent::ToggleSpriteLight { output_events.emit_server(ToggleSpriteLightEvent {
entity: data.entity, entity: data.entity,
pos: self.static_data.sprite_pos, pos: self.static_data.sprite_pos,
enable, enable,
}) })
}, },
_ => output_events _ => 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) { if matches!(self.static_data.sprite_kind, SpriteInteractKind::Unlock) {

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate}, comp::{character_state::OutputEvents, CharacterState, StateUpdate},
event::{LocalEvent, ServerEvent}, event::{CreateSpriteEvent, LocalEvent},
outcome::Outcome, outcome::Outcome,
spiral::Spiral2d, spiral::Spiral2d,
states::{ states::{
@ -173,7 +173,7 @@ impl CharacterBehavior for Data {
}; };
for i in 0..layers { for i in 0..layers {
// Send server event to create sprite // 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), pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i),
sprite: self.static_data.sprite, sprite: self.static_data.sprite,
del_timeout: self.static_data.del_timeout, del_timeout: self.static_data.del_timeout,

View File

@ -10,7 +10,7 @@ use crate::{
}, },
CharacterState, InventoryManip, StateUpdate, CharacterState, InventoryManip, StateUpdate,
}, },
event::ServerEvent, event::{BuffEvent, InventoryManipEvent},
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -141,11 +141,11 @@ impl CharacterBehavior for Data {
if matches!(update.character, CharacterState::Roll(_)) { if matches!(update.character, CharacterState::Roll(_)) {
// Remove potion/saturation effect if left the use item state early by rolling // 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, entity: data.entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Potion), buff_change: BuffChange::RemoveByKind(BuffKind::Potion),
}); });
output_events.emit_server(ServerEvent::Buff { output_events.emit_server(BuffEvent {
entity: data.entity, entity: data.entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Saturation), 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 { if item_is_same {
// Create inventory manipulation event // Create inventory manipulation event
let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot)); 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));
} }
} }

View File

@ -19,7 +19,7 @@ use crate::{
StateUpdate, StateUpdate,
}, },
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent}, event::{BuffEvent, ChangeStanceEvent, ComboChangeEvent, InventoryManipEvent, LocalEvent},
mounting::Volume, mounting::Volume,
outcome::Outcome, outcome::Outcome,
states::{behavior::JoinData, utils::CharacterState::Idle, *}, states::{behavior::JoinData, utils::CharacterState::Idle, *},
@ -1058,7 +1058,7 @@ pub fn handle_manipulate_loadout(
} else { } else {
// Else emit inventory action instantaneously // Else emit inventory action instantaneously
let inv_manip = InventoryManip::Use(slot); 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) => { 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 // For inventory actions without a dedicated character state, just do action instantaneously
InventoryAction::Swap(equip, slot) => { InventoryAction::Swap(equip, slot) => {
let inv_manip = InventoryManip::Swap(Slot::Equip(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) => { InventoryAction::Drop(equip) => {
let inv_manip = InventoryManip::Drop(Slot::Equip(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 => { InventoryAction::Sort => {
output_events.emit_server(ServerEvent::InventoryManip( output_events.emit_server(InventoryManipEvent(data.entity, InventoryManip::Sort));
data.entity,
InventoryManip::Sort,
));
}, },
InventoryAction::Use(slot @ Slot::Equip(_)) => { InventoryAction::Use(slot @ Slot::Equip(_)) => {
let inv_manip = InventoryManip::Use(slot); 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(_)) => { InventoryAction::Use(Slot::Overflow(_)) => {
// Items in overflow slots cannot be used until moved to a real slot // 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 { if let Some(init_event) = ability.ability_meta().init_event {
match init_event { match init_event {
AbilityInitEvent::EnterStance(stance) => { AbilityInitEvent::EnterStance(stance) => {
output_events.emit_server(ServerEvent::ChangeStance { output_events.emit_server(ChangeStanceEvent {
entity: data.entity, entity: data.entity,
stance, stance,
}); });
@ -1614,7 +1611,7 @@ impl HandInfo {
pub fn leave_stance(data: &JoinData<'_>, output_events: &mut OutputEvents) { pub fn leave_stance(data: &JoinData<'_>, output_events: &mut OutputEvents) {
if !matches!(data.stance, Some(Stance::None)) { if !matches!(data.stance, Some(Stance::None)) {
output_events.emit_server(ServerEvent::ChangeStance { output_events.emit_server(ChangeStanceEvent {
entity: data.entity, entity: data.entity,
stance: Stance::None, stance: Stance::None,
}); });
@ -1653,7 +1650,7 @@ impl ComboConsumption {
Self::All => combo, Self::All => combo,
Self::Half => (combo + 1) / 2, Self::Half => (combo + 1) / 2,
}; };
output_events.emit_server(ServerEvent::ComboChange { output_events.emit_server(ComboChangeEvent {
entity: data.entity, entity: data.entity,
change: -(to_consume as i32), change: -(to_consume as i32),
}); });
@ -1663,13 +1660,13 @@ impl ComboConsumption {
fn loadout_change_hook(data: &JoinData<'_>, output_events: &mut OutputEvents, clear_combo: bool) { fn loadout_change_hook(data: &JoinData<'_>, output_events: &mut OutputEvents, clear_combo: bool) {
if clear_combo { if clear_combo {
// Reset combo to 0 // Reset combo to 0
output_events.emit_server(ServerEvent::ComboChange { output_events.emit_server(ComboChangeEvent {
entity: data.entity, entity: data.entity,
change: -data.combo.map_or(0, |c| c.counter() as i32), change: -data.combo.map_or(0, |c| c.counter() as i32),
}); });
} }
// Clear any buffs from equipped weapons // Clear any buffs from equipped weapons
output_events.emit_server(ServerEvent::Buff { output_events.emit_server(BuffEvent {
entity: data.entity, entity: data.entity,
buff_change: BuffChange::RemoveByCategory { buff_change: BuffChange::RemoveByCategory {
all_required: vec![BuffCategory::RemoveOnLoadoutChange], all_required: vec![BuffCategory::RemoveOnLoadoutChange],

View File

@ -8,4 +8,4 @@ mod special_areas;
mod state; mod state;
// TODO: breakup state module and remove glob // TODO: breakup state module and remove glob
pub use special_areas::*; pub use special_areas::*;
pub use state::{BlockChange, BlockDiff, State, TerrainChanges}; pub use state::{BlockChange, BlockDiff, ScheduledBlockChange, State, TerrainChanges};

View File

@ -8,7 +8,7 @@ use common::uid::IdMaps;
use common::{ use common::{
calendar::Calendar, calendar::Calendar,
comp, comp,
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent},
link::Is, link::Is,
mounting::{Mount, Rider, VolumeRider, VolumeRiders}, mounting::{Mount, Rider, VolumeRider, VolumeRiders},
outcome::Outcome, outcome::Outcome,
@ -331,7 +331,6 @@ impl State {
ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool)); ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
// TODO: only register on the server // TODO: only register on the server
ecs.insert(EventBus::<ServerEvent>::default());
ecs.insert(comp::group::GroupManager::default()); ecs.insert(comp::group::GroupManager::default());
ecs.insert(SysMetrics::default()); ecs.insert(SysMetrics::default());
ecs.insert(PhysicsMetrics::default()); ecs.insert(PhysicsMetrics::default());
@ -417,6 +416,15 @@ impl State {
self.ecs.read_storage().get(entity).copied() self.ecs.read_storage().get(entity).copied()
} }
/// # Panics
/// Panics if `EventBus<E>` is borrowed
pub fn emit_event_now<E>(&self, event: E)
where
EventBus<E>: Resource,
{
self.ecs.write_resource::<EventBus<E>>().emit_now(event)
}
/// Given mutable access to the resource R, assuming the resource /// Given mutable access to the resource R, assuming the resource
/// component exists (this is already the behavior of functions like `fetch` /// component exists (this is already the behavior of functions like `fetch`
/// and `write_component_ignore_entity_dead`). Since all of our resources /// and `write_component_ignore_entity_dead`). Since all of our resources
@ -685,9 +693,7 @@ impl State {
self.dispatcher.dispatch(&self.ecs); self.dispatcher.dispatch(&self.ecs);
drop(guard); drop(guard);
section_span!(guard, "maintain ecs"); self.maintain_ecs();
self.ecs.maintain();
drop(guard);
if update_terrain { if update_terrain {
self.apply_terrain_changes_internal(true, block_update); self.apply_terrain_changes_internal(true, block_update);
@ -730,6 +736,11 @@ impl State {
drop(guard); drop(guard);
} }
pub fn maintain_ecs(&mut self) {
span!(_guard, "maintain ecs");
self.ecs.maintain();
}
/// Clean up the state after a tick. /// Clean up the state after a tick.
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
span!(_guard, "cleanup", "State::cleanup"); span!(_guard, "cleanup", "State::cleanup");

View File

@ -6,19 +6,27 @@ use common::{
group::Group, group::Group,
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats, Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats,
}, },
event::{Emitter, EventBus, ServerEvent}, event::{AuraEvent, BuffEvent, EmitExt},
event_emitters,
resources::Time, resources::Time,
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{shred, Entities, Entity as EcsEntity, Join, Read, ReadStorage, SystemData}; use specs::{shred, Entities, Entity as EcsEntity, Join, Read, ReadStorage, SystemData};
event_emitters! {
struct Events[Emitters] {
aura: AuraEvent,
buff: BuffEvent,
}
}
#[derive(SystemData)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
players: ReadStorage<'a, Player>, players: ReadStorage<'a, Player>,
time: Read<'a, Time>, time: Read<'a, Time>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
id_maps: Read<'a, IdMaps>, id_maps: Read<'a, IdMaps>,
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>, cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
@ -42,7 +50,7 @@ impl<'a> System<'a> for Sys {
const PHASE: Phase = Phase::Create; const PHASE: Phase = Phase::Create;
fn run(_job: &mut Job<Self>, read_data: Self::SystemData) { fn run(_job: &mut Job<Self>, 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 // Iterate through all entities with an aura
for (entity, pos, auras_comp, uid) in ( for (entity, pos, auras_comp, uid) in (
@ -118,14 +126,14 @@ impl<'a> System<'a> for Sys {
target_buffs, target_buffs,
stats, stats,
&read_data, &read_data,
&mut server_emitter, &mut emitters,
); );
} }
} }
}); });
} }
if !expired_auras.is_empty() { if !expired_auras.is_empty() {
server_emitter.emit(ServerEvent::Aura { emitters.emit(AuraEvent {
entity, entity,
aura_change: AuraChange::RemoveByKey(expired_auras), aura_change: AuraChange::RemoveByKey(expired_auras),
}); });
@ -145,7 +153,7 @@ fn activate_aura(
target_buffs: &Buffs, target_buffs: &Buffs,
stats: Option<&Stats>, stats: Option<&Stats>,
read_data: &ReadData, read_data: &ReadData,
server_emitter: &mut Emitter<ServerEvent>, emitters: &mut impl EmitExt<BuffEvent>,
) { ) {
let should_activate = match aura.aura_kind { let should_activate = match aura.aura_kind {
AuraKind::Buff { kind, source, .. } => { AuraKind::Buff { kind, source, .. } => {
@ -223,7 +231,7 @@ fn activate_aura(
&& buff.data.strength >= data.strength && buff.data.strength >= data.strength
}); });
if emit_buff { if emit_buff {
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity: target, entity: target,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
kind, kind,

View File

@ -5,7 +5,8 @@ use common::{
Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori, Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
Player, Pos, Scale, Stats, Player, Pos, Scale, Stats,
}, },
event::{EventBus, ServerEvent}, event::{self, EmitExt, EventBus},
event_emitters,
outcome::Outcome, outcome::Outcome,
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
terrain::TerrainGrid, terrain::TerrainGrid,
@ -21,11 +22,24 @@ use specs::{
}; };
use vek::*; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
players: ReadStorage<'a, Player>, players: ReadStorage<'a, Player>,
server_bus: Read<'a, EventBus<ServerEvent>>,
time: Read<'a, Time>, time: Read<'a, Time>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
terrain: ReadExpect<'a, TerrainGrid>, terrain: ReadExpect<'a, TerrainGrid>,
@ -46,6 +60,7 @@ pub struct ReadData<'a> {
character_states: ReadStorage<'a, CharacterState>, character_states: ReadStorage<'a, CharacterState>,
buffs: ReadStorage<'a, Buffs>, buffs: ReadStorage<'a, Buffs>,
outcomes: Read<'a, EventBus<Outcome>>, outcomes: Read<'a, EventBus<Outcome>>,
events: ReadAttackEvents<'a>,
} }
/// This system is responsible for handling beams that heal or do damage /// 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; const PHASE: Phase = Phase::Create;
fn run(job: &mut Job<Self>, (read_data, mut beams): Self::SystemData) { fn run(job: &mut Job<Self>, (read_data, mut beams): Self::SystemData) {
let mut server_emitter = read_data.server_bus.emitter();
let mut outcomes_emitter = read_data.outcomes.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); job.cpu_stats.measure(ParMode::Rayon);
// Beams // 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.entities,
&read_data.positions, &read_data.positions,
&read_data.orientations, &read_data.orientations,
@ -109,14 +124,14 @@ impl<'a> System<'a> for Sys {
) )
.par_join() .par_join()
.fold( .fold(
|| (Vec::new(), Vec::new(), Vec::new()), || (read_data.events.get_emitters(), Vec::new(), Vec::new()),
|(mut server_events, mut add_hit_entities, mut outcomes), |(mut emitters, mut add_hit_entities, mut outcomes),
(entity, pos, ori, uid, beam)| { (entity, pos, ori, uid, beam)| {
// Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing // Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing
// this becomes a bottleneck we can look into alternatives. // this becomes a bottleneck we can look into alternatives.
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
if rng.gen_bool(0.005) { 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), 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, 1.0,
AttackSource::Beam, AttackSource::Beam,
*read_data.time, *read_data.time,
|e| server_events.push(e), &mut emitters,
|o| outcomes.push(o), |o| outcomes.push(o),
&mut rng, &mut rng,
0, 0,
@ -282,14 +297,14 @@ impl<'a> System<'a> for Sys {
add_hit_entities.push((entity, target)); add_hit_entities.push((entity, target));
} }
}); });
(server_events, add_hit_entities, outcomes) (emitters, add_hit_entities, outcomes)
}, },
) )
.reduce( .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_a, mut hit_entities_a, mut outcomes_a),
(mut events_b, mut hit_entities_b, mut outcomes_b)| { (events_b, mut hit_entities_b, mut outcomes_b)| {
events_a.append(&mut events_b); events_a.append(events_b);
hit_entities_a.append(&mut hit_entities_b); hit_entities_a.append(&mut hit_entities_b);
outcomes_a.append(&mut outcomes_b); outcomes_a.append(&mut outcomes_b);
(events_a, hit_entities_a, outcomes_a) (events_a, hit_entities_a, outcomes_a)
@ -298,7 +313,6 @@ impl<'a> System<'a> for Sys {
job.cpu_stats.measure(ParMode::Single); job.cpu_stats.measure(ParMode::Single);
outcomes_emitter.emit_many(new_outcomes); outcomes_emitter.emit_many(new_outcomes);
server_emitter.emit_many(server_events);
for (entity, hit_entity) in add_hit_entities { for (entity, hit_entity) in add_hit_entities {
if let Some(ref mut beam) = beams.get_mut(entity) { if let Some(ref mut beam) = beams.get_mut(entity) {

View File

@ -13,7 +13,11 @@ use common::{
Energy, Group, Health, HealthChange, Inventory, LightEmitter, ModifierKind, PhysicsState, Energy, Group, Health, HealthChange, Inventory, LightEmitter, ModifierKind, PhysicsState,
Pos, Stats, Pos, Stats,
}, },
event::{Emitter, EventBus, ServerEvent}, event::{
BuffEvent, ChangeBodyEvent, CreateSpriteEvent, EmitExt, EnergyChangeEvent,
HealthChangeEvent, RemoveLightEmitterEvent, SoundEvent,
},
event_emitters,
outcome::Outcome, outcome::Outcome,
resources::{DeltaTime, Secs, Time}, resources::{DeltaTime, Secs, Time},
terrain::SpriteKind, terrain::SpriteKind,
@ -29,12 +33,24 @@ use specs::{
}; };
use vek::Vec3; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
outcome_bus: Read<'a, EventBus<Outcome>>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
healths: ReadStorage<'a, Health>, healths: ReadStorage<'a, Health>,
energies: ReadStorage<'a, Energy>, energies: ReadStorage<'a, Energy>,
@ -60,8 +76,7 @@ impl<'a> System<'a> for Sys {
const PHASE: Phase = Phase::Create; const PHASE: Phase = Phase::Create;
fn run(job: &mut Job<Self>, (read_data, mut stats): Self::SystemData) { fn run(job: &mut Job<Self>, (read_data, mut stats): Self::SystemData) {
let mut server_emitter = read_data.server_bus.emitter(); let mut emitters = read_data.events.get_emitters();
let mut outcome = read_data.outcome_bus.emitter();
let dt = read_data.dt.0; let dt = read_data.dt.0;
// Set to false to avoid spamming server // Set to false to avoid spamming server
stats.set_event_emission(false); stats.set_event_emission(false);
@ -116,11 +131,11 @@ impl<'a> System<'a> for Sys {
// slower than parallel checking above // slower than parallel checking above
for e in to_put_out_campfires { for e in to_put_out_campfires {
{ {
server_emitter.emit(ServerEvent::ChangeBody { emitters.emit(ChangeBodyEvent {
entity: e, entity: e,
new_body: Body::Object(object::Body::Campfire), 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) Some(SpriteKind::EnsnaringVines) | Some(SpriteKind::EnsnaringWeb)
) { ) {
// If on ensnaring vines, apply ensnared debuff // If on ensnaring vines, apply ensnared debuff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Ensnared, BuffKind::Ensnared,
@ -161,7 +176,7 @@ impl<'a> System<'a> for Sys {
Some(SpriteKind::SeaUrchin) Some(SpriteKind::SeaUrchin)
) { ) {
// If touching Sea Urchin apply Bleeding buff // If touching Sea Urchin apply Bleeding buff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Bleeding, BuffKind::Bleeding,
@ -181,17 +196,17 @@ impl<'a> System<'a> for Sys {
// TODO: Determine a better place to emit sprite change events // TODO: Determine a better place to emit sprite change events
if let Some(pos) = read_data.positions.get(entity) { if let Some(pos) = read_data.positions.get(entity) {
// If touching Trap - change sprite and apply Bleeding buff // 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), pos: Vec3::new(pos.0.x as i32, pos.0.y as i32, pos.0.z as i32 - 1),
sprite: SpriteKind::HaniwaTrapTriggered, sprite: SpriteKind::HaniwaTrapTriggered,
del_timeout: Some((4.0, 1.0)), 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), 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, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Bleeding, BuffKind::Bleeding,
@ -209,7 +224,7 @@ impl<'a> System<'a> for Sys {
Some(SpriteKind::IronSpike | SpriteKind::HaniwaTrapTriggered) Some(SpriteKind::IronSpike | SpriteKind::HaniwaTrapTriggered)
) { ) {
// If touching Iron Spike apply Bleeding buff // If touching Iron Spike apply Bleeding buff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Bleeding, BuffKind::Bleeding,
@ -226,7 +241,7 @@ impl<'a> System<'a> for Sys {
Some(SpriteKind::HotSurface) Some(SpriteKind::HotSurface)
) { ) {
// If touching a hot surface apply Burning buff // If touching a hot surface apply Burning buff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Burning, BuffKind::Burning,
@ -243,7 +258,7 @@ impl<'a> System<'a> for Sys {
Some(SpriteKind::IceSpike) Some(SpriteKind::IceSpike)
) { ) {
// When standing on IceSpike, apply bleeding // When standing on IceSpike, apply bleeding
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Bleeding, BuffKind::Bleeding,
@ -255,7 +270,7 @@ impl<'a> System<'a> for Sys {
)), )),
}); });
// When standing on IceSpike also apply Frozen // When standing on IceSpike also apply Frozen
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Frozen, BuffKind::Frozen,
@ -272,7 +287,7 @@ impl<'a> System<'a> for Sys {
Some(SpriteKind::FireBlock) Some(SpriteKind::FireBlock)
) { ) {
// If on FireBlock vines, apply burning buff // If on FireBlock vines, apply burning buff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Burning, BuffKind::Burning,
@ -293,7 +308,7 @@ impl<'a> System<'a> for Sys {
}) })
) { ) {
// If in lava fluid, apply burning debuff // If in lava fluid, apply burning debuff
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
BuffKind::Burning, BuffKind::Burning,
@ -313,7 +328,7 @@ impl<'a> System<'a> for Sys {
) && buff_comp.kinds[BuffKind::Burning].is_some() ) && buff_comp.kinds[BuffKind::Burning].is_some()
{ {
// If in water fluid and currently burning, remove burning debuffs // If in water fluid and currently burning, remove burning debuffs
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::RemoveByKind(BuffKind::Burning), buff_change: BuffChange::RemoveByKind(BuffKind::Burning),
}); });
@ -363,7 +378,7 @@ impl<'a> System<'a> for Sys {
}; };
if replace { if replace {
expired_buffs.push(buff_key); expired_buffs.push(buff_key);
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::Add(Buff::new( buff_change: BuffChange::Add(Buff::new(
buff.kind, buff.kind,
@ -458,7 +473,7 @@ impl<'a> System<'a> for Sys {
energy, energy,
entity, entity,
buff_owner, buff_owner,
&mut server_emitter, &mut emitters,
dt, dt,
*read_data.time, *read_data.time,
expired_buffs.contains(&buff_key), expired_buffs.contains(&buff_key),
@ -472,12 +487,12 @@ impl<'a> System<'a> for Sys {
// Update body if needed. // Update body if needed.
let new_body = body_override.unwrap_or(stat.original_body); let new_body = body_override.unwrap_or(stat.original_body);
if new_body != *body { if new_body != *body {
server_emitter.emit(ServerEvent::ChangeBody { entity, new_body }); emitters.emit(ChangeBodyEvent { entity, new_body });
} }
// Remove buffs that expire // Remove buffs that expire
if !expired_buffs.is_empty() { if !expired_buffs.is_empty() {
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::RemoveByKey(expired_buffs), buff_change: BuffChange::RemoveByKey(expired_buffs),
}); });
@ -485,7 +500,7 @@ impl<'a> System<'a> for Sys {
// Remove buffs that don't persist on death // Remove buffs that don't persist on death
if health.is_dead { if health.is_dead {
server_emitter.emit(ServerEvent::Buff { emitters.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::RemoveByCategory { buff_change: BuffChange::RemoveByCategory {
all_required: vec![], all_required: vec![],
@ -515,7 +530,9 @@ fn execute_effect(
energy: &Energy, energy: &Energy,
entity: Entity, entity: Entity,
buff_owner: Option<Uid>, buff_owner: Option<Uid>,
server_emitter: &mut Emitter<ServerEvent>, server_emitter: &mut (
impl EmitExt<HealthChangeEvent> + EmitExt<EnergyChangeEvent> + EmitExt<BuffEvent>
),
dt: f32, dt: f32,
time: Time, time: Time,
buff_will_expire: bool, buff_will_expire: bool,
@ -577,7 +594,7 @@ fn execute_effect(
DamageContributor::new(uid, read_data.groups.get(entity).cloned()) DamageContributor::new(uid, read_data.groups.get(entity).cloned())
}) })
}); });
server_emitter.emit(ServerEvent::HealthChange { server_emitter.emit(HealthChangeEvent {
entity, entity,
change: HealthChange { change: HealthChange {
amount, amount,
@ -602,7 +619,7 @@ fn execute_effect(
ModifierKind::Additive => amount, ModifierKind::Additive => amount,
ModifierKind::Multiplicative => energy.maximum() * amount, ModifierKind::Multiplicative => energy.maximum() * amount,
}; };
server_emitter.emit(ServerEvent::EnergyChange { server_emitter.emit(EnergyChangeEvent {
entity, entity,
change: amount, change: amount,
}); });
@ -718,7 +735,7 @@ fn execute_effect(
}, },
BuffEffect::BuffImmunity(buff_kind) => { BuffEffect::BuffImmunity(buff_kind) => {
if buffs_comp.contains(*buff_kind) { if buffs_comp.contains(*buff_kind) {
server_emitter.emit(ServerEvent::Buff { server_emitter.emit(BuffEvent {
entity, entity,
buff_change: BuffChange::RemoveByKind(*buff_kind), buff_change: BuffChange::RemoveByKind(*buff_kind),
}); });

View File

@ -5,13 +5,13 @@ use specs::{
use common::{ use common::{
comp::{ comp::{
self, self,
character_state::OutputEvents, character_state::{CharacterStateEvents, OutputEvents},
inventory::item::{tool::AbilityMap, MaterialStatManifest}, inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density, ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos,
Scale, SkillSet, Stance, StateUpdate, Stats, Vel, Scale, SkillSet, Stance, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{self, EventBus, KnockbackEvent, LocalEvent},
link::Is, link::Is,
mounting::{Rider, VolumeRider}, mounting::{Rider, VolumeRider},
outcome::Outcome, outcome::Outcome,
@ -28,7 +28,7 @@ use common_ecs::{Job, Origin, Phase, System};
#[derive(SystemData)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: CharacterStateEvents<'a>,
local_bus: Read<'a, EventBus<LocalEvent>>, local_bus: Read<'a, EventBus<LocalEvent>>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
time: Read<'a, Time>, time: Read<'a, Time>,
@ -96,13 +96,12 @@ impl<'a> System<'a> for Sys {
outcomes, outcomes,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = read_data.server_bus.emitter();
let mut local_emitter = read_data.local_bus.emitter(); let mut local_emitter = read_data.local_bus.emitter();
let mut outcomes_emitter = outcomes.emitter(); let mut outcomes_emitter = outcomes.emitter();
let mut emitters = read_data.events.get_emitters();
let mut local_events = Vec::new(); let mut local_events = Vec::new();
let mut server_events = Vec::new(); let mut output_events = OutputEvents::new(&mut local_events, &mut emitters);
let mut output_events = OutputEvents::new(&mut local_events, &mut server_events);
let join = ( let join = (
&read_data.entities, &read_data.entities,
@ -179,7 +178,7 @@ impl<'a> System<'a> for Sys {
state: poise_state, state: poise_state,
}); });
if let Some(impulse_strength) = impulse_strength { if let Some(impulse_strength) = impulse_strength {
server_emitter.emit(ServerEvent::Knockback { output_events.emit_server(KnockbackEvent {
entity, entity,
impulse: impulse_strength * *poise.knockback(), impulse: impulse_strength * *poise.knockback(),
}); });
@ -255,7 +254,6 @@ impl<'a> System<'a> for Sys {
}); });
local_emitter.append_vec(local_events); local_emitter.append_vec(local_events);
server_emitter.append_vec(server_events);
} }
} }
@ -297,7 +295,7 @@ impl Sys {
join.controller.queued_inputs.remove(&input); join.controller.queued_inputs.remove(&input);
} }
if state_update.swap_equipped_weapons { if state_update.swap_equipped_weapons {
output_events.emit_server(ServerEvent::InventoryManip( output_events.emit_server(event::InventoryManipEvent(
join.entity, join.entity,
InventoryManip::SwapEquippedWeapons, InventoryManip::SwapEquippedWeapons,
)); ));

View File

@ -4,7 +4,8 @@ use common::{
agent::{Sound, SoundKind}, agent::{Sound, SoundKind},
Body, BuffChange, Collider, ControlEvent, Controller, Pos, Scale, Body, BuffChange, Collider, ControlEvent, Controller, Pos, Scale,
}, },
event::{EventBus, ServerEvent}, event::{self, EmitExt},
event_emitters,
terrain::TerrainGrid, terrain::TerrainGrid,
uid::IdMaps, uid::IdMaps,
}; };
@ -12,11 +13,33 @@ use common_ecs::{Job, Origin, Phase, System};
use specs::{shred, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, WriteStorage}; use specs::{shred, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, WriteStorage};
use vek::*; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
id_maps: Read<'a, IdMaps>, id_maps: Read<'a, IdMaps>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
terrain_grid: ReadExpect<'a, TerrainGrid>, terrain_grid: ReadExpect<'a, TerrainGrid>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
@ -35,7 +58,7 @@ impl<'a> System<'a> for Sys {
const PHASE: Phase = Phase::Create; const PHASE: Phase = Phase::Create;
fn run(_job: &mut Job<Self>, (read_data, mut controllers): Self::SystemData) { fn run(_job: &mut Job<Self>, (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() { for (entity, controller) in (&read_data.entities, &mut controllers).join() {
// Sanitize inputs to avoid clients sending bad data // Sanitize inputs to avoid clients sending bad data
@ -46,7 +69,7 @@ impl<'a> System<'a> for Sys {
match event { match event {
ControlEvent::Mount(mountee_uid) => { ControlEvent::Mount(mountee_uid) => {
if let Some(mountee_entity) = read_data.id_maps.uid_entity(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) => { ControlEvent::MountVolume(volume) => {
@ -56,51 +79,49 @@ impl<'a> System<'a> for Sys {
&read_data.colliders, &read_data.colliders,
) { ) {
if block.is_mountable() { if block.is_mountable() {
server_emitter.emit(ServerEvent::MountVolume(entity, volume)); emitters.emit(event::MountVolumeEvent(entity, volume));
} }
} }
}, },
ControlEvent::SetPetStay(pet_uid, stay) => { ControlEvent::SetPetStay(pet_uid, stay) => {
if let Some(pet_entity) = read_data.id_maps.uid_entity(pet_uid) { 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) => { ControlEvent::RemoveBuff(buff_id) => {
server_emitter.emit(ServerEvent::Buff { emitters.emit(event::BuffEvent {
entity, entity,
buff_change: BuffChange::RemoveFromController(buff_id), buff_change: BuffChange::RemoveFromController(buff_id),
}); });
}, },
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), ControlEvent::Unmount => emitters.emit(event::UnmountEvent(entity)),
ControlEvent::EnableLantern => { ControlEvent::EnableLantern => {
server_emitter.emit(ServerEvent::EnableLantern(entity)) emitters.emit(event::SetLanternEvent(entity, true))
}, },
ControlEvent::DisableLantern => { ControlEvent::DisableLantern => {
server_emitter.emit(ServerEvent::DisableLantern(entity)) emitters.emit(event::SetLanternEvent(entity, false))
}, },
ControlEvent::Interact(npc_uid, subject) => { ControlEvent::Interact(npc_uid, subject) => {
if let Some(npc_entity) = read_data.id_maps.uid_entity(npc_uid) { if let Some(npc_entity) = read_data.id_maps.uid_entity(npc_uid) {
server_emitter emitters.emit(event::NpcInteractEvent(entity, npc_entity, subject));
.emit(ServerEvent::NpcInteract(entity, npc_entity, subject));
} }
}, },
ControlEvent::InitiateInvite(inviter_uid, kind) => { 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) => { ControlEvent::InviteResponse(response) => {
server_emitter.emit(ServerEvent::InviteResponse(entity, response)); emitters.emit(event::InviteResponseEvent(entity, response));
}, },
ControlEvent::PerformTradeAction(trade_id, action) => { ControlEvent::PerformTradeAction(trade_id, action) => {
server_emitter emitters.emit(event::ProcessTradeActionEvent(entity, trade_id, action));
.emit(ServerEvent::ProcessTradeAction(entity, trade_id, action));
}, },
ControlEvent::InventoryEvent(event) => { ControlEvent::InventoryEvent(event) => {
server_emitter.emit(ServerEvent::InventoryManip(entity, event.into())); emitters.emit(event::InventoryManipEvent(entity, event.into()));
}, },
ControlEvent::GroupManip(manip) => { 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) => { ControlEvent::Utterance(kind) => {
if let (Some(pos), Some(body), scale) = ( if let (Some(pos), Some(body), scale) = (
read_data.positions.get(entity), 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 8.0, // TODO: Come up with a better way of determining this
1.0, 1.0,
); );
server_emitter.emit(ServerEvent::Sound { sound }); emitters.emit(event::SoundEvent { sound });
} }
}, },
ControlEvent::ChangeAbility { ControlEvent::ChangeAbility {
@ -122,7 +143,7 @@ impl<'a> System<'a> for Sys {
auxiliary_key, auxiliary_key,
new_ability, new_ability,
} => { } => {
server_emitter.emit(ServerEvent::ChangeAbility { emitters.emit(event::ChangeAbilityEvent {
entity, entity,
slot, slot,
auxiliary_key, auxiliary_key,
@ -130,14 +151,14 @@ impl<'a> System<'a> for Sys {
}); });
}, },
ControlEvent::LeaveStance => { ControlEvent::LeaveStance => {
server_emitter.emit(ServerEvent::ChangeStance { emitters.emit(event::ChangeStanceEvent {
entity, entity,
stance: Stance::None, stance: Stance::None,
}); });
}, },
ControlEvent::ActivatePortal(portal_uid) => { ControlEvent::ActivatePortal(portal_uid) => {
if let Some(portal) = read_data.id_maps.uid_entity(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 });
} }
}, },
} }

View File

@ -6,7 +6,8 @@ use common::{
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Melee,
Ori, Player, Pos, Scale, Stats, Ori, Player, Pos, Scale, Stats,
}, },
event::{EventBus, ServerEvent}, event::{self, EmitExt, EventBus},
event_emitters,
outcome::Outcome, outcome::Outcome,
resources::Time, resources::Time,
terrain::TerrainGrid, terrain::TerrainGrid,
@ -22,6 +23,21 @@ use specs::{
}; };
use vek::*; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
time: Read<'a, Time>, time: Read<'a, Time>,
@ -40,10 +56,10 @@ pub struct ReadData<'a> {
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
groups: ReadStorage<'a, Group>, groups: ReadStorage<'a, Group>,
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
server_bus: Read<'a, EventBus<ServerEvent>>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
combos: ReadStorage<'a, Combo>, combos: ReadStorage<'a, Combo>,
buffs: ReadStorage<'a, Buffs>, buffs: ReadStorage<'a, Buffs>,
events: ReadAttackEvents<'a>,
} }
/// This system is responsible for handling accepted inputs like moving or /// 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; const PHASE: Phase = Phase::Create;
fn run(_job: &mut Job<Self>, (read_data, mut melee_attacks, outcomes): Self::SystemData) { fn run(_job: &mut Job<Self>, (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 outcomes_emitter = outcomes.emitter();
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -82,7 +98,7 @@ impl<'a> System<'a> for Sys {
if melee_attack.applied { if melee_attack.applied {
continue; continue;
} }
server_emitter.emit(ServerEvent::Sound { emitters.emit(event::SoundEvent {
sound: Sound::new(SoundKind::Melee, pos.0, 2.0, read_data.time.0), sound: Sound::new(SoundKind::Melee, pos.0, 2.0, read_data.time.0),
}); });
melee_attack.applied = true; 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)) if eye_pos.distance_squared(block_pos.map(|e| e as f32 + 0.5))
< (rad + scale * melee_attack.range).powi(2) < (rad + scale * melee_attack.range).powi(2)
{ {
server_emitter.emit(ServerEvent::MineBlock { emitters.emit(event::MineBlockEvent {
entity: attacker, entity: attacker,
pos: block_pos, pos: block_pos,
tool, tool,
@ -270,7 +286,7 @@ impl<'a> System<'a> for Sys {
strength, strength,
AttackSource::Melee, AttackSource::Melee,
*read_data.time, *read_data.time,
|e| server_emitter.emit(e), &mut emitters,
|o| outcomes_emitter.emit(o), |o| outcomes_emitter.emit(o),
&mut rng, &mut rng,
offset as u64, offset as u64,

View File

@ -7,7 +7,8 @@ use common::{
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel, PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
}, },
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY}, consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent}, event::{EmitExt, EventBus, LandOnGroundEvent},
event_emitters,
link::Is, link::Is,
mounting::{Rider, VolumeRider}, mounting::{Rider, VolumeRider},
outcome::Outcome, outcome::Outcome,
@ -123,6 +124,12 @@ fn calc_z_limit(char_state_maybe: Option<&CharacterState>, collider: &Collider)
collider.get_z_limits(modifier) collider.get_z_limits(modifier)
} }
event_emitters! {
struct Events[Emitters] {
land_on_ground: LandOnGroundEvent,
}
}
/// This system applies forces and calculates new positions and velocities. /// This system applies forces and calculates new positions and velocities.
#[derive(Default)] #[derive(Default)]
pub struct Sys; pub struct Sys;
@ -130,10 +137,10 @@ pub struct Sys;
#[derive(SystemData)] #[derive(SystemData)]
pub struct PhysicsRead<'a> { pub struct PhysicsRead<'a> {
entities: Entities<'a>, entities: Entities<'a>,
events: Events<'a>,
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
terrain: ReadExpect<'a, TerrainGrid>, terrain: ReadExpect<'a, TerrainGrid>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
event_bus: Read<'a, EventBus<ServerEvent>>,
game_mode: ReadExpect<'a, GameMode>, game_mode: ReadExpect<'a, GameMode>,
scales: ReadStorage<'a, Scale>, scales: ReadStorage<'a, Scale>,
stickies: ReadStorage<'a, Sticky>, stickies: ReadStorage<'a, Sticky>,
@ -1364,16 +1371,16 @@ impl<'a> PhysicsData<'a> {
} }
drop(guard); drop(guard);
let mut event_emitter = read.event_bus.emitter(); let mut emitters = read.events.get_emitters();
land_on_grounds emitters.emit_many(
.into_iter() land_on_grounds
.for_each(|(entity, vel, surface_normal)| { .into_iter()
event_emitter.emit(ServerEvent::LandOnGround { .map(|(entity, vel, surface_normal)| LandOnGroundEvent {
entity, entity,
vel: vel.0, vel: vel.0,
surface_normal, surface_normal,
}); }),
}); );
} }
fn update_cached_spatial_grid(&mut self) { fn update_cached_spatial_grid(&mut self) {

View File

@ -5,7 +5,12 @@ use common::{
projectile, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, projectile, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health,
Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel, 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, outcome::Outcome,
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
@ -25,6 +30,24 @@ use vek::*;
use common::terrain::TerrainGrid; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
time: Read<'a, Time>, time: Read<'a, Time>,
@ -32,7 +55,7 @@ pub struct ReadData<'a> {
players: ReadStorage<'a, Player>, players: ReadStorage<'a, Player>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
id_maps: Read<'a, IdMaps>, id_maps: Read<'a, IdMaps>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
alignments: ReadStorage<'a, Alignment>, alignments: ReadStorage<'a, Alignment>,
@ -69,7 +92,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
(read_data, mut orientations, mut projectiles, outcomes): Self::SystemData, (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 outcomes_emitter = outcomes.emitter();
let mut rng = rand::thread_rng(); 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)); .and_then(|uid| read_data.id_maps.uid_entity(uid));
if physics.on_surface().is_none() && rng.gen_bool(0.05) { 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), 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, &read_data,
&mut projectile_vanished, &mut projectile_vanished,
&mut outcomes_emitter, &mut outcomes_emitter,
&mut server_emitter, &mut emitters,
&mut rng, &mut rng,
); );
} }
@ -200,18 +223,18 @@ impl<'a> System<'a> for Sys {
.get(entity) .get(entity)
.map_or_else(Vec3::zero, |ori| ori.look_vec()); .map_or_else(Vec3::zero, |ori| ori.look_vec());
let offset = -0.2 * projectile_direction; let offset = -0.2 * projectile_direction;
server_emitter.emit(ServerEvent::Explosion { emitters.emit(ExplosionEvent {
pos: pos.0 + offset, pos: pos.0 + offset,
explosion: e, explosion: e,
owner: projectile.owner, owner: projectile.owner,
}); });
}, },
projectile::Effect::Vanish => { projectile::Effect::Vanish => {
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
projectile_vanished = true; projectile_vanished = true;
}, },
projectile::Effect::Bonk => { projectile::Effect::Bonk => {
server_emitter.emit(ServerEvent::Bonk { emitters.emit(BonkEvent {
pos: pos.0, pos: pos.0,
owner: projectile.owner, owner: projectile.owner,
target: None, target: None,
@ -231,7 +254,7 @@ impl<'a> System<'a> for Sys {
} }
if projectile.time_left == Duration::default() { if projectile.time_left == Duration::default() {
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
} }
projectile.time_left = projectile projectile.time_left = projectile
.time_left .time_left
@ -264,7 +287,7 @@ fn dispatch_hit(
read_data: &ReadData, read_data: &ReadData,
projectile_vanished: &mut bool, projectile_vanished: &mut bool,
outcomes_emitter: &mut Emitter<Outcome>, outcomes_emitter: &mut Emitter<Outcome>,
server_emitter: &mut Emitter<ServerEvent>, emitters: &mut Emitters,
rng: &mut rand::rngs::ThreadRng, rng: &mut rand::rngs::ThreadRng,
) { ) {
match projectile_info.effect { match projectile_info.effect {
@ -437,7 +460,7 @@ fn dispatch_hit(
1.0, 1.0,
AttackSource::Projectile, AttackSource::Projectile,
*read_data.time, *read_data.time,
|e| server_emitter.emit(e), emitters,
|o| outcomes_emitter.emit(o), |o| outcomes_emitter.emit(o),
rng, rng,
0, 0,
@ -446,7 +469,7 @@ fn dispatch_hit(
projectile::Effect::Explode(e) => { projectile::Effect::Explode(e) => {
let Pos(pos) = *projectile_info.pos; let Pos(pos) = *projectile_info.pos;
let owner_uid = projectile_info.owner_uid; let owner_uid = projectile_info.owner_uid;
server_emitter.emit(ServerEvent::Explosion { emitters.emit(ExplosionEvent {
pos, pos,
explosion: e, explosion: e,
owner: owner_uid, owner: owner_uid,
@ -455,7 +478,7 @@ fn dispatch_hit(
projectile::Effect::Bonk => { projectile::Effect::Bonk => {
let Pos(pos) = *projectile_info.pos; let Pos(pos) = *projectile_info.pos;
let owner_uid = projectile_info.owner_uid; let owner_uid = projectile_info.owner_uid;
server_emitter.emit(ServerEvent::Bonk { emitters.emit(BonkEvent {
pos, pos,
owner: owner_uid, owner: owner_uid,
target: Some(projectile_target_info.uid), target: Some(projectile_target_info.uid),
@ -463,7 +486,7 @@ fn dispatch_hit(
}, },
projectile::Effect::Vanish => { projectile::Effect::Vanish => {
let entity = projectile_info.entity; let entity = projectile_info.entity;
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
*projectile_vanished = true; *projectile_vanished = true;
}, },
projectile::Effect::Possess => { projectile::Effect::Possess => {
@ -471,7 +494,7 @@ fn dispatch_hit(
let owner_uid = projectile_info.owner_uid; let owner_uid = projectile_info.owner_uid;
if let Some(owner_uid) = owner_uid { if let Some(owner_uid) = owner_uid {
if target_uid != owner_uid { if target_uid != owner_uid {
server_emitter.emit(ServerEvent::Possess(owner_uid, target_uid)); emitters.emit(PossessEvent(owner_uid, target_uid));
} }
} }
}, },

View File

@ -6,7 +6,12 @@ use common::{
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, 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, outcome::Outcome,
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
@ -18,10 +23,26 @@ use rand::Rng;
use specs::{shred, Entities, Join, LendJoin, Read, ReadStorage, SystemData, WriteStorage}; use specs::{shred, Entities, Join, LendJoin, Read, ReadStorage, SystemData, WriteStorage};
use vek::*; 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)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
time: Read<'a, Time>, time: Read<'a, Time>,
players: ReadStorage<'a, Player>, players: ReadStorage<'a, Player>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
@ -63,7 +84,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
(read_data, mut shockwaves, mut shockwave_hit_lists, outcomes): Self::SystemData, (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 outcomes_emitter = outcomes.emitter();
let mut rng = rand::thread_rng(); 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)); .and_then(|uid| read_data.id_maps.uid_entity(uid));
if rng.gen_bool(0.05) { if rng.gen_bool(0.05) {
server_emitter.emit(ServerEvent::Sound { emitters.emit(SoundEvent {
sound: Sound::new(SoundKind::Shockwave, pos.0, 40.0, time), 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 // 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 // may have traveled and produced effects a bit before reaching it's end point
if time > end_time { if time > end_time {
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
continue; continue;
} }
@ -249,7 +270,7 @@ impl<'a> System<'a> for Sys {
1.0, 1.0,
shockwave.dodgeable.to_attack_source(), shockwave.dodgeable.to_attack_source(),
*read_data.time, *read_data.time,
|e| server_emitter.emit(e), &mut emitters,
|o| outcomes_emitter.emit(o), |o| outcomes_emitter.emit(o),
&mut rng, &mut rng,
0, 0,

View File

@ -7,7 +7,8 @@ use common::{
Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats,
StatsModifier, StatsModifier,
}, },
event::{EventBus, ServerEvent}, event::{DestroyEvent, EmitExt},
event_emitters,
resources::{DeltaTime, EntitiesDiedLastTick, Time}, resources::{DeltaTime, EntitiesDiedLastTick, Time},
}; };
use common_ecs::{Job, Origin, Phase, System}; 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 SIT_ENERGY_REGEN_ACCEL: f32 = 2.5;
const POISE_REGEN_ACCEL: f32 = 2.0; const POISE_REGEN_ACCEL: f32 = 2.0;
event_emitters! {
struct Events[Emitters] {
destroy: DestroyEvent,
}
}
#[derive(SystemData)] #[derive(SystemData)]
pub struct ReadData<'a> { pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
time: Read<'a, Time>, time: Read<'a, Time>,
server_bus: Read<'a, EventBus<ServerEvent>>, events: Events<'a>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
@ -65,7 +72,7 @@ impl<'a> System<'a> for Sys {
): Self::SystemData, ): Self::SystemData,
) { ) {
entities_died_last_tick.0.clear(); 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; let dt = read_data.dt.0;
// Update stats // Update stats
@ -84,7 +91,7 @@ impl<'a> System<'a> for Sys {
if set_dead { if set_dead {
let cloned_entity = (entity, *pos); let cloned_entity = (entity, *pos);
entities_died_last_tick.0.push(cloned_entity); entities_died_last_tick.0.push(cloned_entity);
server_event_emitter.emit(ServerEvent::Destroy { emitters.emit(DestroyEvent {
entity, entity,
cause: health.last_change, cause: health.last_change,
}); });

View File

@ -3,7 +3,7 @@ use crate::{
AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST, AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST,
PARTIAL_PATH_DIST, SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE, 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::{ util::{
aim_projectile, are_our_owners_hostile, entities_have_line_of_sight, get_attacker, 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, get_entity_by_id, is_dead_or_invulnerable, is_dressed_as_cultist, is_invulnerable,
@ -28,7 +28,7 @@ use common::{
}, },
consts::MAX_MOUNT_RANGE, consts::MAX_MOUNT_RANGE,
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
event::{Emitter, ServerEvent}, event::{ChatEvent, EmitExt, SoundEvent},
mounting::VolumePos, mounting::VolumePos,
path::TraversalConfig, path::TraversalConfig,
rtsim::NpcActivity, rtsim::NpcActivity,
@ -180,7 +180,7 @@ impl<'a> AgentData<'a> {
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData, read_data: &ReadData,
event_emitter: &mut Emitter<ServerEvent>, emitters: &mut AgentEmitters,
rng: &mut impl Rng, rng: &mut impl Rng,
) { ) {
enum ActionTimers { enum ActionTimers {
@ -430,7 +430,7 @@ impl<'a> AgentData<'a> {
agent, agent,
controller, controller,
read_data, read_data,
event_emitter, emitters,
AgentData::is_hunting_animal, AgentData::is_hunting_animal,
); );
} }
@ -807,7 +807,7 @@ impl<'a> AgentData<'a> {
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData, read_data: &ReadData,
event_emitter: &mut Emitter<ServerEvent>, emitters: &mut AgentEmitters,
is_enemy: fn(&Self, EcsEntity, &ReadData) -> bool, is_enemy: fn(&Self, EcsEntity, &ReadData) -> bool,
) { ) {
enum ActionStateTimers { enum ActionStateTimers {
@ -856,7 +856,7 @@ impl<'a> AgentData<'a> {
self.chat_npc_if_allowed_to_speak( self.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-ambush"), Content::localized("npc-speech-ambush"),
agent, agent,
event_emitter, emitters,
); );
aggro_on = true; aggro_on = true;
Some((entity, true)) Some((entity, true))
@ -1666,13 +1666,13 @@ impl<'a> AgentData<'a> {
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData, read_data: &ReadData,
event_emitter: &mut Emitter<ServerEvent>, emitters: &mut AgentEmitters,
rng: &mut impl Rng, rng: &mut impl Rng,
) { ) {
agent.forget_old_sounds(read_data.time.0); agent.forget_old_sounds(read_data.time.0);
if is_invulnerable(*self.entity, read_data) { 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; return;
} }
@ -1705,13 +1705,13 @@ impl<'a> AgentData<'a> {
} else if self.below_flee_health(agent) || !follows_threatening_sounds { } else if self.below_flee_health(agent) || !follows_threatening_sounds {
self.flee(agent, controller, read_data, &sound_pos); self.flee(agent, controller, read_data, &sound_pos);
} else { } else {
self.idle(agent, controller, read_data, event_emitter, rng); self.idle(agent, controller, read_data, emitters, rng);
} }
} else { } else {
self.idle(agent, controller, read_data, event_emitter, rng); self.idle(agent, controller, read_data, emitters, rng);
} }
} else { } 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, agent: &mut Agent,
read_data: &ReadData, read_data: &ReadData,
controller: &mut Controller, controller: &mut Controller,
event_emitter: &mut Emitter<ServerEvent>, emitters: &mut AgentEmitters,
rng: &mut impl Rng, rng: &mut impl Rng,
) { ) {
if let Some(Target { target, .. }) = agent.target { if let Some(Target { target, .. }) = agent.target {
@ -1750,7 +1750,7 @@ impl<'a> AgentData<'a> {
Some(tgt_pos.0), Some(tgt_pos.0),
)); ));
self.idle(agent, controller, read_data, event_emitter, rng); self.idle(agent, controller, read_data, emitters, rng);
} else { } else {
let target_data = TargetData::new(tgt_pos, target, read_data); let target_data = TargetData::new(tgt_pos, target, read_data);
// TODO: Reimplement this in rtsim // TODO: Reimplement this in rtsim
@ -1774,25 +1774,23 @@ impl<'a> AgentData<'a> {
&self, &self,
msg: Content, msg: Content,
agent: &Agent, agent: &Agent,
event_emitter: &mut Emitter<'_, ServerEvent>, emitters: &mut AgentEmitters,
) -> bool { ) -> bool {
if agent.allowed_to_speak() { if agent.allowed_to_speak() {
self.chat_npc(msg, event_emitter); self.chat_npc(msg, emitters);
true true
} else { } else {
false false
} }
} }
pub fn chat_npc(&self, content: Content, event_emitter: &mut Emitter<'_, ServerEvent>) { pub fn chat_npc(&self, content: Content, emitters: &mut AgentEmitters) {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc( emitters.emit(ChatEvent(UnresolvedChatMsg::npc(*self.uid, content)));
*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 { if let Some(body) = self.body {
event_emitter.emit(ServerEvent::Sound { emitters.emit(SoundEvent {
sound: Sound::new( sound: Sound::new(
SoundKind::Utterance(UtteranceKind::Scream, *body), SoundKind::Utterance(UtteranceKind::Scream, *body),
self.pos.0, self.pos.0,
@ -1803,12 +1801,7 @@ impl<'a> AgentData<'a> {
} }
} }
pub fn cry_out( pub fn cry_out(&self, agent: &Agent, emitters: &mut AgentEmitters, read_data: &ReadData) {
&self,
agent: &Agent,
event_emitter: &mut Emitter<'_, ServerEvent>,
read_data: &ReadData,
) {
let has_enemy_alignment = matches!(self.alignment, Some(Alignment::Enemy)); let has_enemy_alignment = matches!(self.alignment, Some(Alignment::Enemy));
if has_enemy_alignment { if has_enemy_alignment {
@ -1817,28 +1810,24 @@ impl<'a> AgentData<'a> {
self.chat_npc_if_allowed_to_speak( self.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-cultist_low_health_fleeing"), Content::localized("npc-speech-cultist_low_health_fleeing"),
agent, agent,
event_emitter, emitters,
); );
} else if is_villager(self.alignment) { } else if is_villager(self.alignment) {
self.chat_npc_if_allowed_to_speak( self.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_under_attack"), Content::localized("npc-speech-villager_under_attack"),
agent, 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( pub fn exclaim_relief_about_enemy_dead(&self, agent: &Agent, emitters: &mut AgentEmitters) {
&self,
agent: &Agent,
event_emitter: &mut Emitter<'_, ServerEvent>,
) {
if is_villager(self.alignment) { if is_villager(self.alignment) {
self.chat_npc_if_allowed_to_speak( self.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_enemy_killed"), Content::localized("npc-speech-villager_enemy_killed"),
agent, agent,
event_emitter, emitters,
); );
} }
} }
@ -2002,7 +1991,7 @@ impl<'a> AgentData<'a> {
controller: &mut Controller, controller: &mut Controller,
target: EcsEntity, target: EcsEntity,
read_data: &ReadData, read_data: &ReadData,
event_emitter: &mut Emitter<ServerEvent>, emitters: &mut AgentEmitters,
rng: &mut impl Rng, rng: &mut impl Rng,
remembers_fight_with_target: bool, remembers_fight_with_target: bool,
) { ) {
@ -2011,7 +2000,7 @@ impl<'a> AgentData<'a> {
let move_dir_mag = move_dir.magnitude(); let move_dir_mag = move_dir.magnitude();
let small_chance = rng.gen::<f32>() < read_data.dt.0 * 0.25; let small_chance = rng.gen::<f32>() < read_data.dt.0 * 0.25;
let mut chat = |content: Content| { 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 mut chat_villager_remembers_fighting = || {
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone()); let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());

View File

@ -17,6 +17,7 @@ use common::{
Stats, Vel, Stats, Vel,
}, },
consts::GRAVITY, consts::GRAVITY,
event, event_emitters,
link::Is, link::Is,
mounting::{Mount, Rider, VolumeRider}, mounting::{Mount, Rider, VolumeRider},
path::TraversalConfig, path::TraversalConfig,
@ -28,6 +29,14 @@ use common::{
}; };
use specs::{shred, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData}; 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 // TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
// crate // crate
pub struct AgentData<'a> { pub struct AgentData<'a> {

View File

@ -40,7 +40,10 @@ use common::{
}, },
depot, depot,
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{
ClientDisconnectEvent, CreateWaypointEvent, EventBus, ExplosionEvent, GroupManipEvent,
InitiateInviteEvent, TamePetEvent,
},
generation::{EntityConfig, EntityInfo}, generation::{EntityConfig, EntityInfo},
link::Is, link::Is,
mounting::{Rider, Volume, VolumeRider}, mounting::{Rider, Volume, VolumeRider},
@ -490,6 +493,7 @@ fn handle_drop_all(
pos.0.y + rng.gen_range(5.0..10.0), pos.0.y + rng.gen_range(5.0..10.0),
pos.0.z + 5.0, pos.0.z + 5.0,
)), )),
comp::Ori::default(),
comp::Vel(vel), comp::Vel(vel),
item, item,
None, None,
@ -1804,9 +1808,7 @@ fn handle_spawn(
// Add to group system if a pet // Add to group system if a pet
if matches!(alignment, comp::Alignment::Owned { .. }) { if matches!(alignment, comp::Alignment::Owned { .. }) {
let server_eventbus = server.state.emit_event_now(TamePetEvent {
server.state.ecs().read_resource::<EventBus<ServerEvent>>();
server_eventbus.emit_now(ServerEvent::TamePet {
owner_entity: target, owner_entity: target,
pet_entity: new_entity, pet_entity: new_entity,
}); });
@ -2079,8 +2081,8 @@ fn handle_spawn_campfire(
server server
.state .state
.ecs() .ecs()
.read_resource::<EventBus<ServerEvent>>() .read_resource::<EventBus<CreateWaypointEvent>>()
.emit_now(ServerEvent::CreateWaypoint(pos.0)); .emit_now(CreateWaypointEvent(pos.0));
server.notify_client( server.notify_client(
client, client,
@ -2907,26 +2909,23 @@ fn handle_explosion(
.read_storage::<Uid>() .read_storage::<Uid>()
.get(target) .get(target)
.copied(); .copied();
server server.state.emit_event_now(ExplosionEvent {
.state pos: pos.0,
.mut_resource::<EventBus<ServerEvent>>() explosion: Explosion {
.emit_now(ServerEvent::Explosion { effects: vec![
pos: pos.0, RadiusEffect::Entity(Effect::Damage(Damage {
explosion: Explosion { source: DamageSource::Explosion,
effects: vec![ kind: DamageKind::Energy,
RadiusEffect::Entity(Effect::Damage(Damage { value: 100.0 * power,
source: DamageSource::Explosion, })),
kind: DamageKind::Energy, RadiusEffect::TerrainDestruction(power, Rgb::black()),
value: 100.0 * power, ],
})), radius: 3.0 * power,
RadiusEffect::TerrainDestruction(power, Rgb::black()), reagent: None,
], min_falloff: 0.0,
radius: 3.0 * power, },
reagent: None, owner,
min_falloff: 0.0, });
},
owner,
});
Ok(()) Ok(())
} }
@ -3261,8 +3260,7 @@ fn handle_group_invite(
server server
.state .state
.mut_resource::<EventBus<ServerEvent>>() .emit_event_now(InitiateInviteEvent(target, uid, InviteKind::Group));
.emit_now(ServerEvent::InitiateInvite(target, uid, InviteKind::Group));
if client != target { if client != target {
server.notify_client( server.notify_client(
@ -3301,8 +3299,7 @@ fn handle_group_kick(
server server
.state .state
.mut_resource::<EventBus<ServerEvent>>() .emit_event_now(GroupManipEvent(target, comp::GroupManip::Kick(uid)));
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Kick(uid)));
Ok(()) Ok(())
} else { } else {
Err(Content::Plain(action.help_string())) Err(Content::Plain(action.help_string()))
@ -3318,8 +3315,7 @@ fn handle_group_leave(
) -> CmdResult<()> { ) -> CmdResult<()> {
server server
.state .state
.mut_resource::<EventBus<ServerEvent>>() .emit_event_now(GroupManipEvent(target, comp::GroupManip::Leave));
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Leave));
Ok(()) Ok(())
} }
@ -3337,11 +3333,7 @@ fn handle_group_promote(
server server
.state .state
.mut_resource::<EventBus<ServerEvent>>() .emit_event_now(GroupManipEvent(target, comp::GroupManip::AssignLeader(uid)));
.emit_now(ServerEvent::GroupManip(
target,
comp::GroupManip::AssignLeader(uid),
));
Ok(()) Ok(())
} else { } else {
Err(Content::Plain(action.help_string())) Err(Content::Plain(action.help_string()))
@ -3944,8 +3936,8 @@ fn kick_player(
); );
server server
.state .state
.mut_resource::<EventBus<ServerEvent>>() .mut_resource::<EventBus<ClientDisconnectEvent>>()
.emit_now(ServerEvent::ClientDisconnect( .emit_now(ClientDisconnectEvent(
target_player, target_player,
comp::DisconnectReason::Kicked, comp::DisconnectReason::Kicked,
)); ));

View File

@ -3,25 +3,24 @@ use crate::{
presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt, presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt,
}; };
use common::{ use common::{
character::CharacterId,
comp::{ comp::{
self, self,
aura::{Aura, AuraKind, AuraTarget}, aura::{Aura, AuraKind, AuraTarget},
buff::{BuffCategory, BuffData, BuffKind, BuffSource}, buff::{BuffCategory, BuffData, BuffKind, BuffSource},
misc::PortalData,
ship::figuredata::VOXEL_COLLIDER_MANIFEST, ship::figuredata::VOXEL_COLLIDER_MANIFEST,
shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos, Alignment, BehaviorCapability, ItemDrops, LightEmitter, Ori, Pos, TradingBehavior, Vel,
Projectile, TradingBehavior, Vel, WaypointArea, WaypointArea,
},
event::{
CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent, CreateShipEvent,
CreateTeleporterEvent, CreateWaypointEvent, EventBus, InitializeCharacterEvent,
InitializeSpectatorEvent, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent,
}, },
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
mounting::{Mounting, Volume, VolumeMounting, VolumePos}, mounting::{Mounting, Volume, VolumeMounting, VolumePos},
outcome::Outcome, outcome::Outcome,
resources::{Secs, Time}, resources::{Secs, Time},
rtsim::RtSimEntity,
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
util::Dir,
vol::IntoFullVolIterator, vol::IntoFullVolIterator,
ViewDistances,
}; };
use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use specs::{Builder, Entity as EcsEntity, WorldExt}; use specs::{Builder, Entity as EcsEntity, WorldExt};
@ -29,56 +28,57 @@ use vek::{Rgb, Vec3};
use super::group_manip::update_map_markers; use super::group_manip::update_map_markers;
pub fn handle_initialize_character( pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) {
server: &mut Server,
entity: EcsEntity,
character_id: CharacterId,
requested_view_distances: ViewDistances,
) {
let updater = server.state.ecs().fetch::<CharacterUpdater>(); let updater = server.state.ecs().fetch::<CharacterUpdater>();
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); drop(updater);
if !pending_database_action { 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 server
.state .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. // Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain { if ev.requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain)); server.notify_client(
ev.entity,
ServerGeneral::SetViewDistance(clamped_vds.terrain),
);
} }
} else { } else {
// A character delete or update was somehow initiated after the login commenced, // 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 // so kick the client out of "ingame" without saving any data and abort
// the character loading process. // the character loading process.
handle_exit_ingame(server, entity, true); handle_exit_ingame(server, ev.entity, true);
} }
} }
pub fn handle_initialize_spectator( pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) {
server: &mut Server, let clamped_vds = ev.1.clamp(server.settings().max_view_distance);
entity: EcsEntity, server.state.initialize_spectator_data(ev.0, clamped_vds);
requested_view_distances: ViewDistances,
) {
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
server.state.initialize_spectator_data(entity, clamped_vds);
// Correct client if its requested VD is too high. // Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain { if ev.1.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(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( pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) {
server: &mut Server, let loaded_components = PersistedComponents {
entity: EcsEntity, body: ev.components.0,
loaded_components: PersistedComponents, stats: ev.components.1,
metadata: UpdateCharacterMetadata, 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 { if let Some(marker) = loaded_components.map_marker {
server.notify_client( server.notify_client(
entity, ev.entity,
ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update( ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
marker.0, marker.0,
))), ))),
@ -87,68 +87,62 @@ pub fn handle_loaded_character_data(
let result_msg = if let Err(err) = server let result_msg = if let Err(err) = server
.state .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)) ServerGeneral::CharacterDataLoadResult(Err(err))
} else { } 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. // 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( pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEntity {
server: &mut Server,
pos: Pos,
ori: Ori,
mut npc: NpcBuilder,
rider: Option<NpcBuilder>,
) -> EcsEntity {
let entity = server let entity = server
.state .state
.create_npc( .create_npc(
pos, ev.pos,
ori, ev.ori,
npc.stats, ev.npc.stats,
npc.skill_set, ev.npc.skill_set,
npc.health, ev.npc.health,
npc.poise, ev.npc.poise,
npc.inventory, ev.npc.inventory,
npc.body, ev.npc.body,
) )
.with(npc.scale); .with(ev.npc.scale);
if let Some(agent) = &mut npc.agent { if let Some(agent) = &mut ev.npc.agent {
if let Alignment::Owned(_) = &npc.alignment { if let Alignment::Owned(_) = &ev.npc.alignment {
agent.behavior.allow(BehaviorCapability::TRADE); agent.behavior.allow(BehaviorCapability::TRADE);
agent.behavior.trading_behavior = TradingBehavior::AcceptFood; 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) entity.with(agent)
} else { } else {
entity 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)) entity.with(ItemDrops(drop_items))
} else { } else {
entity entity
}; };
let entity = if let Some(home_chunk) = npc.anchor { let entity = if let Some(home_chunk) = ev.npc.anchor {
entity.with(home_chunk) entity.with(home_chunk)
} else { } else {
entity entity
}; };
// Rtsim entity added to IdMaps below. // 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 { entity.with(rtsim_entity).with(RepositionOnChunkLoad {
needs_ground: false, needs_ground: false,
}) })
@ -156,7 +150,7 @@ pub fn handle_create_npc(
entity entity
}; };
let entity = if let Some(projectile) = npc.projectile { let entity = if let Some(projectile) = ev.npc.projectile {
entity.with(projectile) entity.with(projectile)
} else { } else {
entity entity
@ -164,7 +158,7 @@ pub fn handle_create_npc(
let new_entity = entity.build(); let new_entity = entity.build();
if let Some(rtsim_entity) = npc.rtsim_entity { if let Some(rtsim_entity) = ev.npc.rtsim_entity {
server server
.state() .state()
.ecs() .ecs()
@ -173,7 +167,7 @@ pub fn handle_create_npc(
} }
// Add to group system if a pet // 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 state = server.state();
let uids = state.ecs().read_storage::<Uid>(); let uids = state.ecs().read_storage::<Uid>();
let clients = state.ecs().read_storage::<Client>(); let clients = state.ecs().read_storage::<Client>();
@ -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::Wild => None,
Alignment::Passive => None, Alignment::Passive => None,
Alignment::Enemy => Some(comp::group::ENEMY), 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); let _ = server.state.ecs().write_storage().insert(new_entity, group);
} }
if let Some(rider) = rider { if let Some(rider) = ev.rider {
let rider_entity = handle_create_npc(server, pos, Ori::default(), rider, None); 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::<Uid>(); let uids = server.state().ecs().read_storage::<Uid>();
let link = Mounting { let link = Mounting {
mount: *uids.get(new_entity).expect("We just created this entity"), mount: *uids.get(new_entity).expect("We just created this entity"),
@ -231,21 +230,13 @@ pub fn handle_create_npc(
new_entity new_entity
} }
pub fn handle_create_ship( pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
server: &mut Server, let collider = ev.ship.make_collider();
pos: Pos,
ori: Ori,
ship: comp::ship::Body,
rtsim_entity: Option<RtSimEntity>,
driver: Option<NpcBuilder>,
passengers: Vec<NpcBuilder>,
) {
let collider = ship.make_collider();
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read(); let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
// TODO: Find better solution for this, maybe something like a serverside block // TODO: Find better solution for this, maybe something like a serverside block
// of interests. // of interests.
let (mut steering, mut seats) = { let (mut steering, mut _seats) = {
let mut steering = Vec::new(); let mut steering = Vec::new();
let mut seats = Vec::new(); let mut seats = Vec::new();
@ -263,7 +254,9 @@ pub fn handle_create_ship(
(steering.into_iter(), seats.into_iter()) (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 { if let Some(mut agent) = agent {
let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship)); let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship));
@ -273,13 +266,18 @@ pub fn handle_create_ship(
entity = entity.with(agent); entity = entity.with(agent);
} }
*/ */
if let Some(rtsim_vehicle) = rtsim_entity { if let Some(rtsim_vehicle) = ev.rtsim_entity {
entity = entity.with(rtsim_vehicle); entity = entity.with(rtsim_vehicle);
} }
let entity = entity.build(); let entity = entity.build();
if let Some(driver) = driver { if let Some(driver) = ev.driver {
let npc_entity = handle_create_npc(server, pos, ori, driver, None); 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::<Uid>(); let uids = server.state.ecs().read_storage::<Uid>();
let (rider_uid, mount_uid) = uids 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( for passenger in ev.passengers {
server, let npc_entity = handle_create_npc(server, CreateNpcEvent {
Pos(pos.0 + Vec3::unit_z() * 5.0), pos: Pos(ev.pos.0 + Vec3::unit_z() * 5.0),
ori, ori: ev.ori,
passenger, npc: passenger,
None, rider: None,
); });
if let Some((rider_pos, rider_block)) = seats.next() { if let Some((rider_pos, rider_block)) = seats.next() {
let uids = server.state.ecs().read_storage::<Uid>(); let uids = server.state.ecs().read_storage::<Uid>();
let (rider_uid, mount_uid) = uids let (rider_uid, mount_uid) = uids
@ -342,62 +340,54 @@ pub fn handle_create_ship(
.expect("Failed to link passanger to ship"); .expect("Failed to link passanger to ship");
} }
} }
*/
} }
pub fn handle_shoot( pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
server: &mut Server,
entity: EcsEntity,
pos: Pos,
dir: Dir,
body: Body,
light: Option<LightEmitter>,
projectile: Projectile,
speed: f32,
object: Option<Object>,
) {
let state = server.state_mut(); 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 + state
.ecs() .ecs()
.read_storage::<Vel>() .read_storage::<Vel>()
.get(entity) .get(ev.entity)
.map_or(Vec3::zero(), |v| v.0); .map_or(Vec3::zero(), |v| v.0);
// Add an outcome // Add an outcome
state state
.ecs() .ecs()
.read_resource::<EventBus<Outcome>>() .read_resource::<EventBus<Outcome>>()
.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); let mut builder = state.create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile);
if let Some(light) = light { if let Some(light) = ev.light {
builder = builder.with(light) builder = builder.with(light)
} }
if let Some(object) = object { if let Some(object) = ev.object {
builder = builder.with(object) builder = builder.with(object)
} }
builder.build(); builder.build();
} }
pub fn handle_shockwave( pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
server: &mut Server,
properties: shockwave::Properties,
pos: Pos,
ori: Ori,
) {
let state = server.state_mut(); 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<f32>) { pub fn handle_create_waypoint(server: &mut Server, ev: CreateWaypointEvent) {
let time = server.state.get_time(); let time = server.state.get_time();
server server
.state .state
.create_object(Pos(pos), comp::object::Body::CampfireLit) .create_object(Pos(ev.0), comp::object::Body::CampfireLit)
.with(LightEmitter { .with(LightEmitter {
col: Rgb::new(1.0, 0.3, 0.1), col: Rgb::new(1.0, 0.3, 0.1),
strength: 5.0, strength: 5.0,
@ -435,9 +425,40 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
.build(); .build();
} }
pub fn handle_create_teleporter(server: &mut Server, pos: Vec3<f32>, portal: PortalData) { pub fn handle_create_teleporter(server: &mut Server, ev: CreateTeleporterEvent) {
server server
.state .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(); .build();
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use crate::{client::Client, Server}; use crate::client::Client;
use common::{ use common::{
comp::{ comp::{
self, self,
@ -6,26 +6,24 @@ use common::{
invite::{InviteKind, PendingInvites}, invite::{InviteKind, PendingInvites},
ChatType, GroupManip, ChatType, GroupManip,
}, },
uid::Uid, event::GroupManipEvent,
}; uid::{IdMaps, Uid},
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use common_state::State;
use specs::{
world::{Entity, WorldExt},
ReadStorage, WriteStorage,
}; };
use common_net::msg::ServerGeneral;
use specs::{world::Entity, Entities, Read, ReadStorage, Write, WriteStorage};
use super::ServerEvent;
pub fn can_invite( pub fn can_invite(
state: &State,
clients: &ReadStorage<'_, Client>, clients: &ReadStorage<'_, Client>,
groups: &ReadStorage<'_, Group>,
group_manager: &GroupManager,
pending_invites: &mut WriteStorage<'_, PendingInvites>, pending_invites: &mut WriteStorage<'_, PendingInvites>,
max_group_size: u32, max_group_size: u32,
inviter: Entity, inviter: Entity,
invitee: Entity, invitee: Entity,
) -> bool { ) -> bool {
// Disallow inviting entity that is already in your group // Disallow inviting entity that is already in your group
let groups = state.ecs().read_storage::<Group>();
let group_manager = state.ecs().read_resource::<GroupManager>();
let already_in_same_group = groups.get(inviter).map_or(false, |group| { let already_in_same_group = groups.get(inviter).map_or(false, |group| {
group_manager group_manager
.group_info(*group) .group_info(*group)
@ -45,9 +43,7 @@ pub fn can_invite(
// Check if group max size is already reached // Check if group max size is already reached
// Adding the current number of pending invites // Adding the current number of pending invites
let group_size_limit_reached = state let group_size_limit_reached = groups
.ecs()
.read_storage()
.get(inviter) .get(inviter)
.copied() .copied()
.and_then(|group| { .and_then(|group| {
@ -112,94 +108,31 @@ pub fn update_map_markers<'a>(
} }
} }
// TODO: turn chat messages into enums impl ServerEvent for GroupManipEvent {
pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) { type SystemData<'a> = (
match manip { Entities<'a>,
GroupManip::Leave => { Write<'a, GroupManager>,
let state = server.state_mut(); Read<'a, IdMaps>,
let clients = state.ecs().read_storage::<Client>(); WriteStorage<'a, Group>,
let uids = state.ecs().read_storage::<Uid>(); ReadStorage<'a, Client>,
let mut group_manager = state.ecs().write_resource::<GroupManager>(); ReadStorage<'a, Uid>,
let map_markers = state.ecs().read_storage::<comp::MapMarker>(); ReadStorage<'a, comp::Alignment>,
group_manager.leave_group( ReadStorage<'a, comp::MapMarker>,
entity, );
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
&state.ecs().entities(),
&mut |entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
},
GroupManip::Kick(uid) => {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let alignments = state.ecs().read_storage::<comp::Alignment>();
let target = match state.ecs().entity_from_uid(uid) { fn handle(
Some(t) => t, events: impl ExactSizeIterator<Item = Self>,
None => { (entities, mut group_manager, id_maps, mut groups, clients, uids, alignments, map_markers): Self::SystemData<'_>,
// Inform of failure ) {
if let Some(client) = clients.get(entity) { for GroupManipEvent(entity, manip) in events {
client.send_fallible(ServerGeneral::server_msg( match manip {
ChatType::Meta, GroupManip::Leave => {
"Kick failed, target does not exist.",
));
}
return;
},
};
// Can't kick pet
if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner))
{
if let Some(general_stream) = clients.get(entity) {
general_stream.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed, you can't kick pets.",
));
}
return;
}
// Can't kick yourself
if uids.get(entity).map_or(false, |u| *u == uid) {
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed, you can't kick yourself.",
));
}
return;
}
let mut groups = state.ecs().write_storage::<Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
// Make sure kicker is the group leader
match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Remove target from group
group_manager.leave_group( group_manager.leave_group(
target, entity,
&mut groups, &mut groups,
&state.ecs().read_storage(), &alignments,
&uids, &uids,
&state.ecs().entities(), &entities,
&mut |entity, group_change| { &mut |entity, group_change| {
clients clients
.get(entity) .get(entity)
@ -214,123 +147,195 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
}); });
}, },
); );
// Tell them the have been kicked
if let Some(client) = clients.get(target) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"You were removed from the group.",
));
}
// Tell kicker that they were successful
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Player kicked.",
));
}
}, },
Some(_) => { GroupManip::Kick(uid) => {
// Inform kicker that they are not the leader let target = match id_maps.uid_entity(uid) {
if let Some(client) = clients.get(entity) { Some(t) => t,
client.send_fallible(ServerGeneral::server_msg( None => {
ChatType::Meta, // Inform of failure
"Kick failed: You are not the leader of the target's group.", if let Some(client) = clients.get(entity) {
)); client.send_fallible(ServerGeneral::server_msg(
} ChatType::Meta,
}, "Kick failed, target does not exist.",
None => { ));
// Inform kicker that the target is not in a group }
if let Some(client) = clients.get(entity) { return;
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed: Your target is not in a group.",
));
}
},
}
},
GroupManip::AssignLeader(uid) => {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let target = match state.ecs().entity_from_uid(uid) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Leadership transfer failed, target does not exist",
));
}
return;
},
};
let groups = state.ecs().read_storage::<Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
// Make sure assigner is the group leader
match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Assign target as group leader
group_manager.assign_leader(
target,
&groups,
&state.ecs().entities(),
&state.ecs().read_storage(),
&uids,
|entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
}, },
); };
// Tell them they are the leader
if let Some(client) = clients.get(target) { // Can't kick pet
client.send_fallible(ServerGeneral::server_msg( if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner))
ChatType::Meta, {
"You are the group leader now.", if let Some(general_stream) = clients.get(entity) {
)); general_stream.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed, you can't kick pets.",
));
}
return;
} }
// Tell the old leader that the transfer was succesful // Can't kick yourself
if let Some(client) = clients.get(entity) { if uids.get(entity).map_or(false, |u| *u == uid) {
client.send_fallible(ServerGeneral::server_msg( if let Some(client) = clients.get(entity) {
ChatType::Meta, client.send_fallible(ServerGeneral::server_msg(
"You are no longer the group leader.", ChatType::Meta,
)); "Kick failed, you can't kick yourself.",
));
}
return;
}
// Make sure kicker is the group leader
match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Remove target from group
group_manager.leave_group(
target,
&mut groups,
&alignments,
&uids,
&entities,
&mut |entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(
&map_markers,
&uids,
c,
&group_change,
);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
// Tell them the have been kicked
if let Some(client) = clients.get(target) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"You were removed from the group.",
));
}
// Tell kicker that they were successful
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Player kicked.",
));
}
},
Some(_) => {
// Inform kicker that they are not the leader
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed: You are not the leader of the target's group.",
));
}
},
None => {
// Inform kicker that the target is not in a group
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Kick failed: Your target is not in a group.",
));
}
},
} }
}, },
Some(_) => { GroupManip::AssignLeader(uid) => {
// Inform transferer that they are not the leader let target = match id_maps.uid_entity(uid) {
if let Some(client) = clients.get(entity) { Some(t) => t,
client.send_fallible(ServerGeneral::server_msg( None => {
ChatType::Meta, // Inform of failure
"Transfer failed: You are not the leader of the target's group.", if let Some(client) = clients.get(entity) {
)); client.send_fallible(ServerGeneral::server_msg(
} ChatType::Meta,
}, "Leadership transfer failed, target does not exist",
None => { ));
// Inform transferer that the target is not in a group }
if let Some(client) = clients.get(entity) { return;
client.send_fallible(ServerGeneral::server_msg( },
ChatType::Meta, };
"Transfer failed: Your target is not in a group.", // Make sure assigner is the group leader
)); match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Assign target as group leader
group_manager.assign_leader(
target,
&groups,
&entities,
&alignments,
&uids,
|entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(
&map_markers,
&uids,
c,
&group_change,
);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
// Tell them they are the leader
if let Some(client) = clients.get(target) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"You are the group leader now.",
));
}
// Tell the old leader that the transfer was succesful
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"You are no longer the group leader.",
));
}
},
Some(_) => {
// Inform transferer that they are not the leader
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Transfer failed: You are not the leader of the target's \
group.",
));
}
},
None => {
// Inform transferer that the target is not in a group
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Transfer failed: Your target is not in a group.",
));
}
},
} }
}, },
} }
}, }
} }
} }

View File

@ -1,52 +1,62 @@
use crate::{client::Client, Server}; use crate::client::Client;
use common::event::RequestSiteInfoEvent;
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral}; use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
use specs::{Entity as EcsEntity, WorldExt}; use specs::{ReadExpect, ReadStorage};
use std::collections::HashMap; use std::collections::HashMap;
use world::IndexOwned;
use super::ServerEvent;
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
pub fn handle_site_info(server: &Server, entity: EcsEntity, id: u64) { impl ServerEvent for RequestSiteInfoEvent {
let info = EconomyInfo { type SystemData<'a> = ReadStorage<'a, Client>;
id,
population: 0, fn handle(events: impl ExactSizeIterator<Item = Self>, clients: Self::SystemData<'_>) {
stock: HashMap::new(), for ev in events {
labor_values: HashMap::new(), if let Some(client) = clients.get(ev.entity) {
values: HashMap::new(), let info = EconomyInfo {
labors: Vec::new(), id: ev.id,
last_exports: HashMap::new(), population: 0,
resources: HashMap::new(), stock: HashMap::new(),
}; labor_values: HashMap::new(),
let msg = ServerGeneral::SiteEconomy(info); values: HashMap::new(),
server labors: Vec::new(),
.state last_exports: HashMap::new(),
.ecs() resources: HashMap::new(),
.read_storage::<Client>() };
.get(entity) let msg = ServerGeneral::SiteEconomy(info);
.map(|c| c.send(msg)); client.send_fallible(msg);
}
}
}
} }
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
pub fn handle_site_info(server: &Server, entity: EcsEntity, id: u64) { impl ServerEvent for RequestSiteInfoEvent {
let site_id = server.index.sites.recreate_id(id); type SystemData<'a> = (ReadExpect<'a, IndexOwned>, ReadStorage<'a, Client>);
let info = if let Some(site_id) = site_id {
let site = server.index.sites.get(site_id); fn handle(events: impl ExactSizeIterator<Item = Self>, (index, clients): Self::SystemData<'_>) {
site.economy.get_information(site_id) for ev in events {
} else { if let Some(client) = clients.get(ev.entity) {
EconomyInfo { let site_id = index.sites.recreate_id(ev.id);
id, let info = if let Some(site_id) = site_id {
population: 0, let site = index.sites.get(site_id);
stock: HashMap::new(), site.economy.get_information(site_id)
labor_values: HashMap::new(), } else {
values: HashMap::new(), EconomyInfo {
labors: Vec::new(), id: ev.id,
last_exports: HashMap::new(), population: 0,
resources: HashMap::new(), stock: HashMap::new(),
labor_values: HashMap::new(),
values: HashMap::new(),
labors: Vec::new(),
last_exports: HashMap::new(),
resources: HashMap::new(),
}
};
let msg = ServerGeneral::SiteEconomy(info);
client.send_fallible(msg);
}
} }
}; }
let msg = ServerGeneral::SiteEconomy(info);
server
.state
.ecs()
.read_storage::<Client>()
.get(entity)
.map(|c| c.send(msg));
} }

View File

@ -1,286 +1,169 @@
use specs::{world::WorldExt, Entity as EcsEntity, Join}; use common_state::{BlockChange, ScheduledBlockChange};
use specs::{DispatcherBuilder, Join, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use vek::*; use vek::*;
use common::{ use common::{
assets::{self, Concatenate}, assets::{self, Concatenate},
comp::{ comp::{
self, self,
agent::{AgentEvent, Sound, SoundKind}, agent::{AgentEvent, SoundKind},
dialogue::Subject,
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
item::{flatten_counted_items, MaterialStatManifest}, item::{flatten_counted_items, MaterialStatManifest},
loot_owner::LootOwnerKind, loot_owner::LootOwnerKind,
pet::is_mountable, tool::AbilityMap,
tool::{AbilityMap, ToolKind},
Inventory, LootOwner, Pos, SkillGroupKind,
}, },
consts::{ consts::{MAX_INTERACT_RANGE, MAX_NPCINTERACT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
MAX_INTERACT_RANGE, MAX_MOUNT_RANGE, MAX_NPCINTERACT_RANGE, MAX_SPRITE_MOUNT_RANGE, event::{
SOUND_TRAVEL_DIST_PER_VOLUME, CreateItemDropEvent, CreateSpriteEvent, EventBus, MineBlockEvent, NpcInteractEvent,
SetLanternEvent, SetPetStayEvent, SoundEvent, TamePetEvent, ToggleSpriteLightEvent,
}, },
event::EventBus,
link::Is, link::Is,
mounting::{Mount, Mounting, Rider, VolumeMounting, VolumePos, VolumeRider}, mounting::Mount,
outcome::Outcome, outcome::Outcome,
rtsim::RtSimEntity, terrain::{Block, SpriteKind, TerrainGrid},
terrain::{Block, SpriteKind}, uid::Uid,
uid::{IdMaps, Uid}, util::Dir,
vol::ReadVol, vol::ReadVol,
}; };
use common_net::sync::WorldSyncExt;
use crate::{rtsim::RtSim, state_ext::StateExt, Server, Time}; use crate::{Server, Time};
use crate::pet::tame_pet; use crate::pet::tame_pet;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::Deserialize; use serde::Deserialize;
use std::{iter::FromIterator, sync::Arc}; use std::iter::FromIterator;
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) { use super::{event_dispatch, mounting::within_mounting_range, ServerEvent};
let ecs = server.state_mut().ecs();
let lantern_exists = ecs pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
.read_storage::<comp::LightEmitter>() event_dispatch::<SetLanternEvent>(builder);
.get(entity) event_dispatch::<NpcInteractEvent>(builder);
.map_or(false, |light| light.strength > 0.0); event_dispatch::<SetPetStayEvent>(builder);
event_dispatch::<MineBlockEvent>(builder);
event_dispatch::<SoundEvent>(builder);
event_dispatch::<CreateSpriteEvent>(builder);
event_dispatch::<ToggleSpriteLightEvent>(builder);
}
if lantern_exists != enable { impl ServerEvent for SetLanternEvent {
if !enable { type SystemData<'a> = (
server WriteStorage<'a, comp::LightEmitter>,
.state_mut() ReadStorage<'a, comp::Inventory>,
.ecs() ReadStorage<'a, comp::Health>,
.write_storage::<comp::LightEmitter>() );
.remove(entity);
} else if ecs // Only enable lantern if entity is alive fn handle(
.read_storage::<comp::Health>() events: impl ExactSizeIterator<Item = Self>,
.get(entity) (mut light_emitters, inventories, healths): Self::SystemData<'_>,
.map_or(true, |h| !h.is_dead) ) {
{ for SetLanternEvent(entity, enable) in events {
let inventory_storage = ecs.read_storage::<Inventory>(); let lantern_exists = light_emitters
let lantern_info = inventory_storage
.get(entity) .get(entity)
.and_then(|inventory| inventory.equipped(EquipSlot::Lantern)) .map_or(false, |light| light.strength > 0.0);
.and_then(|item| {
if let comp::item::ItemKind::Lantern(l) = &*item.kind() { if lantern_exists != enable {
Some((l.color(), l.strength(), l.flicker())) if !enable {
} else { light_emitters.remove(entity);
None }
} // Only enable lantern if entity is alive
}); else if healths.get(entity).map_or(true, |h| !h.is_dead) {
if let Some((col, strength, flicker)) = lantern_info { let lantern_info = inventories
let _ = .get(entity)
ecs.write_storage::<comp::LightEmitter>() .and_then(|inventory| inventory.equipped(EquipSlot::Lantern))
.insert(entity, comp::LightEmitter { .and_then(|item| {
if let comp::item::ItemKind::Lantern(l) = &*item.kind() {
Some((l.color(), l.strength(), l.flicker()))
} else {
None
}
});
if let Some((col, strength, flicker)) = lantern_info {
let _ = light_emitters.insert(entity, comp::LightEmitter {
col, col,
strength, strength,
flicker, flicker,
animated: true, animated: true,
}); });
} }
}
}
}
pub fn handle_npc_interaction(
server: &mut Server,
interactor: EcsEntity,
npc_entity: EcsEntity,
subject: Subject,
) {
let state = server.state_mut();
let within_range = {
let positions = state.ecs().read_storage::<Pos>();
positions
.get(interactor)
.zip(positions.get(npc_entity))
.map_or(false, |(interactor_pos, npc_pos)| {
interactor_pos.0.distance_squared(npc_pos.0) <= MAX_NPCINTERACT_RANGE.powi(2)
})
};
if within_range
&& let Some(agent) = state
.ecs()
.write_storage::<comp::Agent>()
.get_mut(npc_entity)
&& agent.target.is_none()
{
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
agent
.inbox
.push_back(AgentEvent::Talk(interactor_uid, subject));
}
}
}
pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
let state = server.state_mut();
let within_range = {
let positions = state.ecs().read_storage::<Pos>();
within_mounting_range(positions.get(rider), positions.get(mount))
};
if within_range {
let uids = state.ecs().read_storage::<Uid>();
if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied())
{
let is_pet_of = |mount, rider_uid| {
matches!(
state
.ecs()
.read_storage::<comp::Alignment>()
.get(mount),
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
)
};
let can_ride = state
.ecs()
.read_storage()
.get(mount)
.map_or(false, |mount_body| {
is_mountable(mount_body, state.ecs().read_storage().get(rider))
});
let is_stay = state
.ecs()
.read_storage::<comp::Agent>()
.get(mount)
.and_then(|x| x.stay_pos)
.is_some();
if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride && !is_stay
{
drop(uids);
let _ = state.link(Mounting {
mount: mount_uid,
rider: rider_uid,
});
}
}
}
}
pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: VolumePos) {
let state = server.state_mut();
let block_transform = volume_pos.get_block_and_transform(
&state.terrain(),
&state.ecs().read_resource(),
|e| {
state
.read_storage()
.get(e)
.copied()
.zip(state.read_storage().get(e).copied())
},
&state.read_storage(),
);
if let Some((mat, _, block)) = block_transform
&& let Some(mount_offset) = block.mount_offset()
{
let mount_pos = (mat * mount_offset.0.with_w(1.0)).xyz();
let within_range = {
let positions = state.ecs().read_storage::<Pos>();
positions.get(rider).map_or(false, |pos| {
pos.0.distance_squared(mount_pos) < MAX_SPRITE_MOUNT_RANGE.powi(2)
})
};
let maybe_uid = state.ecs().read_storage::<Uid>().get(rider).copied();
if let Some(rider) = maybe_uid
&& within_range
{
let _link_successful = state
.link(VolumeMounting {
pos: volume_pos,
block,
rider,
})
.is_ok();
#[cfg(feature = "worldgen")]
if _link_successful {
let uid_allocator = state.ecs().read_resource::<IdMaps>();
if let Some(rider_entity) = uid_allocator.uid_entity(rider)
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
let entity = uid_allocator.uid_entity(uid)?;
state.read_storage::<RtSimEntity>().get(entity).map(|v| v.0)
})
{
state
.ecs()
.write_resource::<RtSim>()
.hook_character_mount_volume(
&state.ecs().read_resource::<Arc<world::World>>(),
state
.ecs()
.read_resource::<world::IndexOwned>()
.as_index_ref(),
volume_pos,
rider_actor,
);
} }
} }
} }
} }
} }
pub fn handle_unmount(server: &mut Server, rider: EcsEntity) { impl ServerEvent for NpcInteractEvent {
let state = server.state_mut(); type SystemData<'a> = (
state.ecs().write_storage::<Is<Rider>>().remove(rider); WriteStorage<'a, comp::Agent>,
state.ecs().write_storage::<Is<VolumeRider>>().remove(rider); ReadStorage<'a, comp::Pos>,
} ReadStorage<'a, Uid>,
);
pub fn handle_set_pet_stay( fn handle(
server: &mut Server, events: impl ExactSizeIterator<Item = Self>,
command_giver: EcsEntity, (mut agents, positions, uids): Self::SystemData<'_>,
pet: EcsEntity, ) {
stay: bool, for NpcInteractEvent(interactor, npc_entity, subject) in events {
) { let within_range = {
let state = server.state_mut(); positions
let positions = state.ecs().read_storage::<Pos>(); .get(interactor)
let is_owner = state .zip(positions.get(npc_entity))
.ecs() .map_or(false, |(interactor_pos, npc_pos)| {
.uid_from_entity(command_giver) interactor_pos.0.distance_squared(npc_pos.0)
.map_or(false, |owner_uid| { <= MAX_NPCINTERACT_RANGE.powi(2)
matches!( })
state };
.ecs()
.read_storage::<comp::Alignment>()
.get(pet),
Some(comp::Alignment::Owned(pet_owner)) if *pet_owner == owner_uid,
)
});
let current_pet_position = positions.get(pet).copied(); if within_range
let stay = stay && current_pet_position.is_some(); && let Some(agent) = agents.get_mut(npc_entity)
if is_owner && agent.target.is_none()
&& within_mounting_range(positions.get(command_giver), positions.get(pet)) {
&& state.ecs().read_storage::<Is<Mount>>().get(pet).is_none() if let Some(interactor_uid) = uids.get(interactor) {
{ agent
state .inbox
.ecs() .push_back(AgentEvent::Talk(*interactor_uid, subject));
.write_storage::<comp::CharacterActivity>() }
.get_mut(pet) }
.map(|mut activity| activity.is_pet_staying = stay); }
state
.ecs()
.write_storage::<comp::Agent>()
.get_mut(pet)
.map(|s| s.stay_pos = current_pet_position.filter(|_| stay));
} }
} }
fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool { impl ServerEvent for SetPetStayEvent {
match (player_position, mount_position) { type SystemData<'a> = (
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2), WriteStorage<'a, comp::Agent>,
_ => false, WriteStorage<'a, comp::CharacterActivity>,
ReadStorage<'a, comp::Pos>,
ReadStorage<'a, comp::Alignment>,
ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Uid>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut agents, mut character_activities, positions, alignments, is_mounts, uids): Self::SystemData<'_>,
) {
for SetPetStayEvent(command_giver, pet, stay) in events {
let is_owner = uids.get(command_giver).map_or(false, |owner_uid| {
matches!(
alignments.get(pet),
Some(comp::Alignment::Owned(pet_owner)) if *pet_owner == *owner_uid,
)
});
let current_pet_position = positions.get(pet).copied();
let stay = stay && current_pet_position.is_some();
if is_owner
&& within_mounting_range(positions.get(command_giver), positions.get(pet))
&& is_mounts.get(pet).is_none()
{
character_activities
.get_mut(pet)
.map(|mut activity| activity.is_pet_staying = stay);
agents
.get_mut(pet)
.map(|s| s.stay_pos = current_pet_position.filter(|_| stay));
}
}
} }
} }
@ -303,209 +186,249 @@ lazy_static! {
); );
} }
pub fn handle_mine_block( impl ServerEvent for MineBlockEvent {
server: &mut Server, type SystemData<'a> = (
entity: EcsEntity, WriteExpect<'a, BlockChange>,
pos: Vec3<i32>, ReadExpect<'a, TerrainGrid>,
tool: Option<ToolKind>, ReadExpect<'a, MaterialStatManifest>,
) { ReadExpect<'a, AbilityMap>,
let state = server.state_mut(); ReadExpect<'a, EventBus<CreateItemDropEvent>>,
if state.can_set_block(pos) { ReadExpect<'a, EventBus<Outcome>>,
let block = state.terrain().get(pos).ok().copied(); WriteStorage<'a, comp::SkillSet>,
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) { ReadStorage<'a, Uid>,
// Drop item if one is recoverable from the block );
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
let msm = &MaterialStatManifest::load().read();
let ability_map = &AbilityMap::load().read();
let mut items: Vec<_> = flatten_counted_items(&items, ability_map, msm).collect();
let maybe_uid = state.ecs().uid_from_entity(entity);
if let Some(mut skillset) = state fn handle(
.ecs() events: impl ExactSizeIterator<Item = Self>,
.write_storage::<comp::SkillSet>() (
.get_mut(entity) mut block_change,
terrain,
msm,
ability_map,
create_item_drop_events,
outcomes,
mut skill_sets,
uids,
): Self::SystemData<'_>,
) {
use rand::Rng;
let mut rng = rand::thread_rng();
let mut create_item_drop_emitter = create_item_drop_events.emitter();
let mut outcome_emitter = outcomes.emitter();
for ev in events {
if block_change.can_set_block(ev.pos) {
let block = terrain.get(ev.pos).ok().copied();
if let Some(block) =
block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == ev.tool))
{ {
if let (Some(tool), Some(uid), exp_reward @ 1..) = ( // Drop item if one is recoverable from the block
tool, if let Some(items) = comp::Item::try_reclaim_from_block(block) {
maybe_uid, let mut items: Vec<_> =
items flatten_counted_items(&items, &ability_map, &msm).collect();
.iter() let maybe_uid = uids.get(ev.entity).copied();
.filter_map(|item| {
item.item_definition_id().itemdef_id().and_then(|id| { if let Some(mut skillset) = skill_sets.get_mut(ev.entity) {
RESOURCE_EXPERIENCE_MANIFEST.read().0.get(id).copied() if let (Some(tool), Some(uid), exp_reward @ 1..) = (
}) ev.tool,
}) maybe_uid,
.sum(), items
) { .iter()
let skill_group = SkillGroupKind::Weapon(tool); .filter_map(|item| {
let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>(); item.item_definition_id().itemdef_id().and_then(|id| {
if let Some(level_outcome) = RESOURCE_EXPERIENCE_MANIFEST.read().0.get(id).copied()
skillset.add_experience(skill_group, exp_reward) })
{ })
outcome_bus.emit_now(Outcome::SkillPointGain { .sum(),
uid, ) {
skill_tree: skill_group, let skill_group = comp::SkillGroupKind::Weapon(tool);
total_points: level_outcome, if let Some(level_outcome) =
skillset.add_experience(skill_group, exp_reward)
{
outcome_emitter.emit(Outcome::SkillPointGain {
uid,
skill_tree: skill_group,
total_points: level_outcome,
});
}
outcome_emitter.emit(Outcome::ExpChange {
uid,
exp: exp_reward,
xp_pools: HashSet::from_iter(vec![skill_group]),
});
}
use common::comp::skills::{MiningSkill, Skill, SKILL_MODIFIERS};
let need_double_ore = |rng: &mut rand::rngs::ThreadRng| {
let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain);
let skill_level = skillset
.skill_level(Skill::Pick(MiningSkill::OreGain))
.unwrap_or(0);
rng.gen_bool(chance_mod * f64::from(skill_level))
};
let need_double_gem = |rng: &mut rand::rngs::ThreadRng| {
let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.gem_gain);
let skill_level = skillset
.skill_level(Skill::Pick(MiningSkill::GemGain))
.unwrap_or(0);
rng.gen_bool(chance_mod * f64::from(skill_level))
};
for item in items.iter_mut() {
let double_gain =
item.item_definition_id().itemdef_id().map_or(false, |id| {
(id.contains("mineral.ore.") && need_double_ore(&mut rng))
|| (id.contains("mineral.gem.")
&& need_double_gem(&mut rng))
});
if double_gain {
// Ignore non-stackable errors
let _ = item.increase_amount(1);
}
}
}
for item in items {
let loot_owner = maybe_uid
.map(LootOwnerKind::Player)
.map(|owner| comp::LootOwner::new(owner, false));
create_item_drop_emitter.emit(CreateItemDropEvent {
pos: comp::Pos(ev.pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)),
vel: comp::Vel(Vec3::zero()),
ori: comp::Ori::from(Dir::random_2d(&mut rng)),
item,
loot_owner,
}); });
} }
outcome_bus.emit_now(Outcome::ExpChange {
uid,
exp: exp_reward,
xp_pools: HashSet::from_iter(vec![skill_group]),
});
} }
use common::comp::skills::{MiningSkill, Skill, SKILL_MODIFIERS};
use rand::Rng;
let mut rng = rand::thread_rng();
let need_double_ore = |rng: &mut rand::rngs::ThreadRng| { block_change.set(ev.pos, block.into_vacant());
let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain); outcome_emitter.emit(Outcome::BreakBlock {
let skill_level = skillset pos: ev.pos,
.skill_level(Skill::Pick(MiningSkill::OreGain)) color: block.get_color(),
.unwrap_or(0); });
rng.gen_bool(chance_mod * f64::from(skill_level))
};
let need_double_gem = |rng: &mut rand::rngs::ThreadRng| {
let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.gem_gain);
let skill_level = skillset
.skill_level(Skill::Pick(MiningSkill::GemGain))
.unwrap_or(0);
rng.gen_bool(chance_mod * f64::from(skill_level))
};
for item in items.iter_mut() {
let double_gain =
item.item_definition_id().itemdef_id().map_or(false, |id| {
(id.contains("mineral.ore.") && need_double_ore(&mut rng))
|| (id.contains("mineral.gem.") && need_double_gem(&mut rng))
});
if double_gain {
// Ignore non-stackable errors
let _ = item.increase_amount(1);
}
}
}
for item in items {
let loot_owner = maybe_uid
.map(LootOwnerKind::Player)
.map(|owner| LootOwner::new(owner, false));
state.create_item_drop(
Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)),
comp::Vel(Vec3::zero()),
item,
loot_owner,
);
}
}
state.set_block(pos, block.into_vacant());
state
.ecs()
.read_resource::<EventBus<Outcome>>()
.emit_now(Outcome::BreakBlock {
pos,
color: block.get_color(),
});
}
}
}
pub fn handle_sound(server: &mut Server, sound: &Sound) {
let ecs = &server.state.ecs();
let positions = &ecs.read_storage::<Pos>();
let agents = &mut ecs.write_storage::<comp::Agent>();
// TODO: Reduce the complexity of this problem by using spatial partitioning
// system
for (agent, agent_pos) in (agents, positions).join() {
// TODO: Use pathfinding for more dropoff around obstacles
let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos);
let sound_travel_dist_sqrd = (sound.vol * SOUND_TRAVEL_DIST_PER_VOLUME).powi(2);
let vol_dropoff = agent_dist_sqrd / sound_travel_dist_sqrd * sound.vol;
let propagated_sound = sound.with_new_vol(sound.vol - vol_dropoff);
let can_hear_sound = propagated_sound.vol > 0.00;
let should_hear_sound = agent_dist_sqrd < agent.psyche.listen_dist.powi(2);
if can_hear_sound && should_hear_sound {
agent
.inbox
.push_back(AgentEvent::ServerSound(propagated_sound));
}
}
// Attempt to turn this sound into an outcome to be received by frontends.
if let Some(outcome) = match sound.kind {
SoundKind::Utterance(kind, body) => Some(Outcome::Utterance {
kind,
pos: sound.pos,
body,
}),
_ => None,
} {
ecs.read_resource::<EventBus<Outcome>>().emit_now(outcome);
}
}
pub fn handle_create_sprite(
server: &mut Server,
pos: Vec3<i32>,
sprite: SpriteKind,
del_timeout: Option<(f32, f32)>,
) {
let state = server.state_mut();
if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied();
if block.map_or(false, |b| (*b).is_fluid()) {
let old_block = block.unwrap_or_else(|| Block::air(SpriteKind::Empty));
let new_block = old_block.with_sprite(sprite);
state.set_block(pos, new_block);
// Remove sprite after del_timeout and offset if specified
if let Some((timeout, del_offset)) = del_timeout {
use rand::Rng;
let mut rng = rand::thread_rng();
let offset = rng.gen_range(0.0..del_offset);
let current_time: f64 = state.ecs().read_resource::<Time>().0;
let replace_time = current_time + (timeout + offset) as f64;
if old_block != new_block {
server
.state
.schedule_set_block(pos, old_block, new_block, replace_time)
} }
} }
} }
} }
} }
pub fn handle_tame_pet(server: &mut Server, pet_entity: EcsEntity, owner_entity: EcsEntity) { impl ServerEvent for SoundEvent {
type SystemData<'a> = (
ReadExpect<'a, EventBus<Outcome>>,
WriteStorage<'a, comp::Agent>,
ReadStorage<'a, comp::Pos>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(outcomes, mut agents, positions): Self::SystemData<'_>,
) {
let mut outcome_emitter = outcomes.emitter();
for SoundEvent { sound } in events {
// TODO: Reduce the complexity of this problem by using spatial partitioning
// system
for (agent, agent_pos) in (&mut agents, &positions).join() {
// TODO: Use pathfinding for more dropoff around obstacles
let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos);
let sound_travel_dist_sqrd = (sound.vol * SOUND_TRAVEL_DIST_PER_VOLUME).powi(2);
let vol_dropoff = agent_dist_sqrd / sound_travel_dist_sqrd * sound.vol;
let propagated_sound = sound.with_new_vol(sound.vol - vol_dropoff);
let can_hear_sound = propagated_sound.vol > 0.00;
let should_hear_sound = agent_dist_sqrd < agent.psyche.listen_dist.powi(2);
if can_hear_sound && should_hear_sound {
agent
.inbox
.push_back(AgentEvent::ServerSound(propagated_sound));
}
}
// Attempt to turn this sound into an outcome to be received by frontends.
if let Some(outcome) = match sound.kind {
SoundKind::Utterance(kind, body) => Some(Outcome::Utterance {
kind,
pos: sound.pos,
body,
}),
_ => None,
} {
outcome_emitter.emit(outcome);
}
}
}
}
impl ServerEvent for CreateSpriteEvent {
type SystemData<'a> = (
WriteExpect<'a, BlockChange>,
WriteExpect<'a, ScheduledBlockChange>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Time>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut block_change, mut scheduled_block_change, terrain, time): Self::SystemData<'_>,
) {
for ev in events {
if block_change.can_set_block(ev.pos) {
let block = terrain.get(ev.pos).ok().copied();
if block.map_or(false, |b| (*b).is_fluid()) {
let old_block = block.unwrap_or_else(|| Block::air(SpriteKind::Empty));
let new_block = old_block.with_sprite(ev.sprite);
block_change.set(ev.pos, new_block);
// Remove sprite after del_timeout and offset if specified
if let Some((timeout, del_offset)) = ev.del_timeout {
use rand::Rng;
let mut rng = rand::thread_rng();
let offset = rng.gen_range(0.0..del_offset);
let current_time: f64 = time.0;
let replace_time = current_time + (timeout + offset) as f64;
if old_block != new_block {
scheduled_block_change.set(ev.pos, old_block, replace_time);
scheduled_block_change.outcome_set(ev.pos, new_block, replace_time);
}
}
}
}
}
}
}
impl ServerEvent for ToggleSpriteLightEvent {
type SystemData<'a> = (
WriteExpect<'a, BlockChange>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, comp::Pos>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut block_change, terrain, positions): Self::SystemData<'_>,
) {
for ev in events.into_iter() {
if let Some(entity_pos) = positions.get(ev.entity)
&& entity_pos.0.distance_squared(ev.pos.as_()) < MAX_INTERACT_RANGE.powi(2)
&& block_change.can_set_block(ev.pos)
{
if let Some(new_block) = terrain
.get(ev.pos)
.ok()
.and_then(|block| block.with_toggle_light(ev.enable))
{
block_change.set(ev.pos, new_block);
// TODO: Emit outcome
}
}
}
}
}
pub fn handle_tame_pet(server: &mut Server, ev: TamePetEvent) {
// TODO: Raise outcome to send to clients to play sound/render an indicator // TODO: Raise outcome to send to clients to play sound/render an indicator
// showing taming success? // showing taming success?
tame_pet(server.state.ecs(), pet_entity, owner_entity); tame_pet(server.state.ecs(), ev.pet_entity, ev.owner_entity);
}
pub fn handle_toggle_sprite_light(
server: &mut Server,
entity: EcsEntity,
pos: Vec3<i32>,
enable: bool,
) {
let state = server.state_mut();
// TODO: Implement toggling lights on volume entities
if let Some(entity_pos) = state.ecs().read_storage::<Pos>().get(entity)
&& entity_pos.0.distance_squared(pos.as_()) < MAX_INTERACT_RANGE.powi(2)
&& state.can_set_block(pos)
{
if let Some(new_block) = state
.terrain()
.get(pos)
.ok()
.and_then(|block| block.with_toggle_light(enable))
{
state.set_block(pos, new_block);
// TODO: Emit outcome
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,27 @@
use super::group_manip::{self, update_map_markers}; use super::{
use crate::{client::Client, Server}; event_dispatch,
group_manip::{self, update_map_markers},
ServerEvent,
};
use crate::{client::Client, Settings};
use common::{ use common::{
comp::{ comp::{
self, self,
agent::{Agent, AgentEvent}, agent::{Agent, AgentEvent},
group::GroupManager, group::GroupManager,
invite::{Invite, InviteKind, InviteResponse, PendingInvites}, invite::{Invite, InviteKind, InviteResponse, PendingInvites},
ChatType, Pos, ChatType, Group, Pos,
}, },
consts::MAX_TRADE_RANGE, consts::MAX_TRADE_RANGE,
event::{InitiateInviteEvent, InviteResponseEvent},
trade::{TradeResult, Trades}, trade::{TradeResult, Trades},
uid::Uid, uid::{IdMaps, Uid},
}; };
use common_net::{ use common_net::msg::{InviteAnswer, ServerGeneral};
msg::{InviteAnswer, ServerGeneral}, use specs::{
sync::WorldSyncExt, shred, DispatcherBuilder, Entities, Entity, Read, ReadExpect, ReadStorage, SystemData, Write,
WriteStorage,
}; };
use common_state::State;
use specs::{world::WorldExt, Entity};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tracing::{error, warn}; use tracing::{error, warn};
@ -26,206 +30,266 @@ const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
/// Reduced duration shown to the client to help alleviate latency issues /// Reduced duration shown to the client to help alleviate latency issues
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30); const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kind: InviteKind) { pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
let max_group_size = server.settings().max_player_group_size; event_dispatch::<InitiateInviteEvent>(builder);
let state = server.state_mut(); event_dispatch::<InviteResponseEvent>(builder);
let clients = state.ecs().read_storage::<Client>(); }
let invitee = match state.ecs().entity_from_uid(invitee_uid) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Invite failed, target does not exist.",
));
}
return;
},
};
let uids = state.ecs().read_storage::<Uid>(); impl ServerEvent for InitiateInviteEvent {
type SystemData<'a> = (
Write<'a, Trades>,
Read<'a, Settings>,
Read<'a, IdMaps>,
Read<'a, GroupManager>,
WriteStorage<'a, PendingInvites>,
WriteStorage<'a, Agent>,
WriteStorage<'a, Invite>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Client>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Group>,
);
// Check if entity is trying to invite themselves fn handle(
if uids events: impl ExactSizeIterator<Item = Self>,
.get(inviter) (
.map_or(false, |inviter_uid| *inviter_uid == invitee_uid) mut trades,
{ settings,
warn!("Entity tried to invite themselves into a group/trade"); id_maps,
return; group_manager,
} mut pending_invites,
mut agents,
let mut pending_invites = state.ecs().write_storage::<PendingInvites>(); mut invites,
let mut agents = state.ecs().write_storage::<Agent>(); uids,
let mut invites = state.ecs().write_storage::<Invite>(); clients,
positions,
if let InviteKind::Trade = kind { groups,
// Check whether the inviter is in range of the invitee ): Self::SystemData<'_>,
let positions = state.ecs().read_storage::<Pos>(); ) {
if !within_trading_range(positions.get(inviter), positions.get(invitee)) { for InitiateInviteEvent(inviter, invitee_uid, kind) in events {
return; let max_group_size = settings.max_player_group_size;
} let invitee = match id_maps.uid_entity(invitee_uid) {
} Some(t) => t,
None => {
if let InviteKind::Group = kind { // Inform of failure
if !group_manip::can_invite( if let Some(client) = clients.get(inviter) {
state, client.send_fallible(ServerGeneral::server_msg(
&clients, ChatType::Meta,
&mut pending_invites, "Invite failed, target does not exist.",
max_group_size,
inviter,
invitee,
) {
return;
}
} else {
// cancel current trades for inviter before inviting someone else to trade
let mut trades = state.ecs().write_resource::<Trades>();
if let Some(inviter_uid) = uids.get(inviter).copied() {
if let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied() {
trades
.decline_trade(active_trade, inviter_uid)
.and_then(|u| state.ecs().entity_from_uid(u))
.map(|e| {
if let Some(client) = clients.get(e) {
client
.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
}
if let Some(agent) = agents.get_mut(e) {
agent
.inbox
.push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
}
});
}
};
}
if invites.contains(invitee) {
// Inform inviter that there is already an invite
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"This player already has a pending invite.",
));
}
return;
}
let mut invite_sent = false;
// Returns true if insertion was successful
let mut send_invite = || {
match invites.insert(invitee, Invite { inviter, kind }) {
Err(err) => {
error!("Failed to insert Invite component: {:?}", err);
false
},
Ok(_) => {
match pending_invites.entry(inviter) {
Ok(entry) => {
entry.or_insert_with(|| PendingInvites(Vec::new())).0.push((
invitee,
kind,
Instant::now() + INVITE_TIMEOUT_DUR,
)); ));
invite_sent = true; }
true return;
}, },
};
// Check if entity is trying to invite themselves
if uids
.get(inviter)
.map_or(false, |inviter_uid| *inviter_uid == invitee_uid)
{
warn!("Entity tried to invite themselves into a group/trade");
return;
}
if matches!(kind, InviteKind::Trade) {
// Check whether the inviter is in range of the invitee
if !within_trading_range(positions.get(inviter), positions.get(invitee)) {
return;
}
}
if let InviteKind::Group = kind {
if !group_manip::can_invite(
&clients,
&groups,
&group_manager,
&mut pending_invites,
max_group_size,
inviter,
invitee,
) {
return;
}
} else {
// cancel current trades for inviter before inviting someone else to trade
if let Some(inviter_uid) = uids.get(inviter).copied() {
if let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied() {
trades
.decline_trade(active_trade, inviter_uid)
.and_then(|u| id_maps.uid_entity(u))
.map(|e| {
if let Some(client) = clients.get(e) {
client.send_fallible(ServerGeneral::FinishedTrade(
TradeResult::Declined,
));
}
if let Some(agent) = agents.get_mut(e) {
agent.inbox.push_back(AgentEvent::FinishedTrade(
TradeResult::Declined,
));
}
});
}
};
}
if invites.contains(invitee) {
// Inform inviter that there is already an invite
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"This player already has a pending invite.",
));
}
return;
}
let mut invite_sent = false;
// Returns true if insertion was successful
let mut send_invite = || {
match invites.insert(invitee, Invite { inviter, kind }) {
Err(err) => { Err(err) => {
error!( error!("Failed to insert Invite component: {:?}", err);
"Failed to get entry for pending invites component: {:?}",
err
);
// Cleanup
invites.remove(invitee);
false false
}, },
Ok(_) => {
match pending_invites.entry(inviter) {
Ok(entry) => {
entry.or_insert_with(|| PendingInvites(Vec::new())).0.push((
invitee,
kind,
Instant::now() + INVITE_TIMEOUT_DUR,
));
invite_sent = true;
true
},
Err(err) => {
error!(
"Failed to get entry for pending invites component: {:?}",
err
);
// Cleanup
invites.remove(invitee);
false
},
}
},
} }
}, };
}
};
// If client comp // If client comp
if let (Some(client), Some(inviter)) = (clients.get(invitee), uids.get(inviter).copied()) { if let (Some(client), Some(inviter)) =
if send_invite() { (clients.get(invitee), uids.get(inviter).copied())
client.send_fallible(ServerGeneral::Invite { {
inviter, if send_invite() {
timeout: PRESENTED_INVITE_TIMEOUT_DUR, client.send_fallible(ServerGeneral::Invite {
kind, inviter,
}); timeout: PRESENTED_INVITE_TIMEOUT_DUR,
} kind,
} else if let Some(agent) = agents.get_mut(invitee) { });
if send_invite() { }
if let Some(inviter) = uids.get(inviter) { } else if let Some(agent) = agents.get_mut(invitee) {
agent.inbox.push_back(AgentEvent::TradeInvite(*inviter)); if send_invite() {
invite_sent = true; if let Some(inviter) = uids.get(inviter) {
agent.inbox.push_back(AgentEvent::TradeInvite(*inviter));
invite_sent = true;
}
}
} else if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Can't invite, not a player or npc",
));
}
// Notify inviter that the invite is pending
if invite_sent {
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::InvitePending(invitee_uid));
}
} }
} }
} else if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Can't invite, not a player or npc",
));
} }
}
// Notify inviter that the invite is pending #[derive(SystemData)]
if invite_sent { pub struct InviteResponseData<'a> {
if let Some(client) = clients.get(inviter) { entities: Entities<'a>,
client.send_fallible(ServerGeneral::InvitePending(invitee_uid)); group_manager: Write<'a, GroupManager>,
trades: Write<'a, Trades>,
index: ReadExpect<'a, world::IndexOwned>,
id_maps: Read<'a, IdMaps>,
invites: WriteStorage<'a, Invite>,
pending_invites: WriteStorage<'a, PendingInvites>,
groups: WriteStorage<'a, Group>,
agents: WriteStorage<'a, comp::Agent>,
uids: ReadStorage<'a, Uid>,
clients: ReadStorage<'a, Client>,
alignments: ReadStorage<'a, comp::Alignment>,
map_markers: ReadStorage<'a, comp::MapMarker>,
}
impl ServerEvent for InviteResponseEvent {
type SystemData<'a> = InviteResponseData<'a>;
fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
for InviteResponseEvent(entity, response) in events {
match response {
InviteResponse::Accept => handle_invite_accept(&mut data, entity),
InviteResponse::Decline => handle_invite_decline(&mut data, entity),
}
} }
} }
} }
pub fn handle_invite_response(server: &mut Server, entity: Entity, response: InviteResponse) {
match response {
InviteResponse::Accept => handle_invite_accept(server, entity),
InviteResponse::Decline => handle_invite_decline(server, entity),
}
}
pub fn handle_invite_accept(server: &mut Server, entity: Entity) { pub fn handle_invite_accept(data: &mut InviteResponseData, entity: Entity) {
let index = server.index.clone(); if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
let state = server.state_mut(); handle_invite_answer(data, inviter, entity, InviteAnswer::Accepted, kind);
if let Some((inviter, kind)) = get_inviter_and_kind(entity, state) {
handle_invite_answer(state, inviter, entity, InviteAnswer::Accepted, kind);
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut agents = state.ecs().write_storage::<Agent>();
match kind { match kind {
InviteKind::Group => { InviteKind::Group => {
let map_markers = state.ecs().read_storage::<comp::MapMarker>(); data.group_manager.add_group_member(
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
inviter, inviter,
entity, entity,
&state.ecs().entities(), &data.entities,
&mut state.ecs().write_storage(), &mut data.groups,
&state.ecs().read_storage(), &data.alignments,
&uids, &data.uids,
|entity, group_change| { |entity, group_change| {
clients data.clients
.get(entity) .get(entity)
.and_then(|c| { .and_then(|c| {
group_change group_change
.try_map_ref(|e| uids.get(*e).copied()) .try_map_ref(|e| data.uids.get(*e).copied())
.map(|g| (g, c)) .map(|g| (g, c))
}) })
.map(|(g, c)| { .map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change); update_map_markers(&data.map_markers, &data.uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g)); c.send_fallible(ServerGeneral::GroupUpdate(g));
}); });
}, },
); );
}, },
InviteKind::Trade => { InviteKind::Trade => {
if let (Some(inviter_uid), Some(invitee_uid)) = if let (Some(inviter_uid), Some(invitee_uid)) = (
(uids.get(inviter).copied(), uids.get(entity).copied()) data.uids.get(inviter).copied(),
{ data.uids.get(entity).copied(),
let mut trades = state.ecs().write_resource::<Trades>(); ) {
// check if the person that invited me has started a new trade since the // check if the person that invited me has started a new trade since the
// invitation was sent // invitation was sent
if trades.entity_trades.get(&inviter_uid).copied().is_some() { if data
for client in clients.get(entity).into_iter().chain(clients.get(inviter)) { .trades
.entity_trades
.get(&inviter_uid)
.copied()
.is_some()
{
for client in data
.clients
.get(entity)
.into_iter()
.chain(data.clients.get(inviter))
{
client.send_fallible(ServerGeneral::server_msg( client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta, ChatType::Meta,
"Trade failed, inviter initiated new trade since sending trade \ "Trade failed, inviter initiated new trade since sending trade \
@ -234,39 +298,40 @@ pub fn handle_invite_accept(server: &mut Server, entity: Entity) {
} }
return; return;
} }
let id = trades.begin_trade(inviter_uid, invitee_uid); let id = data.trades.begin_trade(inviter_uid, invitee_uid);
let trade = trades.trades[&id].clone(); let trade = data.trades.trades[&id].clone();
if let Some(agent) = agents.get_mut(inviter) { if let Some(agent) = data.agents.get_mut(inviter) {
agent agent
.inbox .inbox
.push_back(AgentEvent::TradeAccepted(invitee_uid)); .push_back(AgentEvent::TradeAccepted(invitee_uid));
} }
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let pricing = agents let pricing = data
.agents
.get(inviter) .get(inviter)
.and_then(|a| { .and_then(|a| {
a.behavior a.behavior
.trade_site() .trade_site()
.and_then(|id| index.get_site_prices(id)) .and_then(|id| data.index.get_site_prices(id))
}) })
.or_else(|| { .or_else(|| {
agents.get(entity).and_then(|a| { data.agents.get(entity).and_then(|a| {
a.behavior a.behavior
.trade_site() .trade_site()
.and_then(|id| index.get_site_prices(id)) .and_then(|id| data.index.get_site_prices(id))
}) })
}); });
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let pricing = None; let pricing = None;
clients.get(inviter).map(|c| { data.clients.get(inviter).map(|c| {
c.send(ServerGeneral::UpdatePendingTrade( c.send(ServerGeneral::UpdatePendingTrade(
id, id,
trade.clone(), trade.clone(),
pricing.clone(), pricing.clone(),
)) ))
}); });
clients data.clients
.get(entity) .get(entity)
.map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing))); .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing)));
} }
@ -275,18 +340,19 @@ pub fn handle_invite_accept(server: &mut Server, entity: Entity) {
} }
} }
fn get_inviter_and_kind(entity: Entity, state: &mut State) -> Option<(Entity, InviteKind)> { fn get_inviter_and_kind(
let mut invites = state.ecs().write_storage::<Invite>(); entity: Entity,
invites.remove(entity).and_then(|invite| { data: &mut InviteResponseData,
) -> Option<(Entity, InviteKind)> {
data.invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite; let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>(); let pending = &mut data.pending_invites.get_mut(inviter)?.0;
let pending = &mut pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list // Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?; let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index); pending.swap_remove(invite_index);
// If no pending invites remain remove the component // If no pending invites remain remove the component
if pending.is_empty() { if pending.is_empty() {
pending_invites.remove(inviter); data.pending_invites.remove(inviter);
} }
Some((inviter, kind)) Some((inviter, kind))
@ -294,28 +360,25 @@ fn get_inviter_and_kind(entity: Entity, state: &mut State) -> Option<(Entity, In
} }
fn handle_invite_answer( fn handle_invite_answer(
state: &mut State, data: &mut InviteResponseData,
inviter: Entity, inviter: Entity,
entity: Entity, entity: Entity,
invite_answer: InviteAnswer, invite_answer: InviteAnswer,
kind: InviteKind, kind: InviteKind,
) { ) {
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
if matches!(kind, InviteKind::Trade) && matches!(invite_answer, InviteAnswer::Accepted) { if matches!(kind, InviteKind::Trade) && matches!(invite_answer, InviteAnswer::Accepted) {
// invitee must close current trade if one exists before accepting new one // invitee must close current trade if one exists before accepting new one
if let Some(invitee_uid) = uids.get(entity).copied() { if let Some(invitee_uid) = data.uids.get(entity).copied() {
let mut trades = state.ecs().write_resource::<Trades>(); if let Some(active_trade) = data.trades.entity_trades.get(&invitee_uid).copied() {
if let Some(active_trade) = trades.entity_trades.get(&invitee_uid).copied() { data.trades
trades
.decline_trade(active_trade, invitee_uid) .decline_trade(active_trade, invitee_uid)
.and_then(|u| state.ecs().entity_from_uid(u)) .and_then(|u| data.id_maps.uid_entity(u))
.map(|e| { .map(|e| {
if let Some(client) = clients.get(e) { if let Some(client) = data.clients.get(e) {
client client
.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined)); .send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
} }
if let Some(agent) = state.ecs().write_storage::<Agent>().get_mut(e) { if let Some(agent) = data.agents.get_mut(e) {
agent agent
.inbox .inbox
.push_back(AgentEvent::FinishedTrade(TradeResult::Declined)); .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
@ -324,7 +387,9 @@ fn handle_invite_answer(
} }
}; };
} }
if let (Some(client), Some(target)) = (clients.get(inviter), uids.get(entity).copied()) { if let (Some(client), Some(target)) =
(data.clients.get(inviter), data.uids.get(entity).copied())
{
client.send_fallible(ServerGeneral::InviteComplete { client.send_fallible(ServerGeneral::InviteComplete {
target, target,
answer: invite_answer, answer: invite_answer,
@ -333,11 +398,10 @@ fn handle_invite_answer(
} }
} }
pub fn handle_invite_decline(server: &mut Server, entity: Entity) { pub fn handle_invite_decline(data: &mut InviteResponseData, entity: Entity) {
let state = server.state_mut(); if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
if let Some((inviter, kind)) = get_inviter_and_kind(entity, state) {
// Inform inviter of rejection // Inform inviter of rejection
handle_invite_answer(state, inviter, entity, InviteAnswer::Declined, kind) handle_invite_answer(data, inviter, entity, InviteAnswer::Declined, kind)
} }
} }

View File

@ -1,43 +1,34 @@
use crate::{ use std::{marker::PhantomData, sync::Arc};
events::{
entity_creation::handle_create_teleporter, use crate::{state_ext::StateExt, Server};
entity_manipulation::{handle_start_teleporting, handle_teleport_to_position}, use common::event::{
interaction::{handle_mount_volume, handle_tame_pet}, ChatEvent, ClientDisconnectEvent, ClientDisconnectWithoutPersistenceEvent, CommandEvent,
}, EventBus, ExitIngameEvent,
persistence::PersistedComponents, };
state_ext::StateExt, use common_base::span;
Server, use specs::{
}; shred::SendDispatcher, DispatcherBuilder, Entity as EcsEntity, ReadExpect, WorldExt,
use common::event::{EventBus, ServerEvent, ServerEventDiscriminants}; WriteExpect,
use common_base::span; };
use entity_creation::{
handle_create_npc, handle_create_ship, handle_create_waypoint, handle_initialize_character,
handle_initialize_spectator, handle_loaded_character_data, handle_shockwave, handle_shoot,
};
use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_change_body,
handle_combo_change, handle_delete, handle_destroy, handle_energy_change,
handle_entity_attacked_hook, handle_explosion, handle_health_change, handle_knockback,
handle_land_on_ground, handle_make_admin, handle_parry_hook, handle_poise,
handle_remove_light_emitter, handle_respawn, handle_stance_change, handle_teleport_to,
handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
use interaction::{
handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction,
handle_set_pet_stay, handle_sound, handle_toggle_sprite_light, handle_unmount,
};
use inventory_manip::handle_inventory;
use invite::{handle_invite, handle_invite_response};
use player::{handle_client_disconnect, handle_exit_ingame, handle_possess};
use specs::{Builder, Entity as EcsEntity, WorldExt};
use trade::handle_process_trade_action;
use crate::events::player::handle_character_delete;
pub use group_manip::update_map_markers; pub use group_manip::update_map_markers;
pub(crate) use trade::cancel_trades_for; pub(crate) use trade::cancel_trades_for;
use self::{
entity_creation::{
handle_create_item_drop, handle_create_npc, handle_create_object, handle_create_ship,
handle_create_teleporter, handle_create_waypoint, handle_initialize_character,
handle_initialize_spectator, handle_loaded_character_data, handle_shockwave, handle_shoot,
},
entity_manipulation::handle_delete,
interaction::handle_tame_pet,
mounting::{handle_mount, handle_mount_volume, handle_unmount},
player::{
handle_character_delete, handle_client_disconnect, handle_exit_ingame, handle_possess,
},
trade::handle_process_trade_action,
};
mod entity_creation; mod entity_creation;
mod entity_manipulation; mod entity_manipulation;
mod group_manip; mod group_manip;
@ -45,9 +36,65 @@ mod information;
mod interaction; mod interaction;
mod inventory_manip; mod inventory_manip;
mod invite; mod invite;
mod mounting;
mod player; mod player;
mod trade; mod trade;
pub trait ServerEvent: Send + Sync + 'static {
type SystemData<'a>: specs::SystemData<'a>;
const NAME: &'static str = std::any::type_name::<Self>();
fn handle(events: impl ExactSizeIterator<Item = Self>, data: Self::SystemData<'_>);
}
struct EventHandler<T>(PhantomData<T>);
impl<T> Default for EventHandler<T> {
fn default() -> Self { Self(PhantomData) }
}
impl<'a, T: ServerEvent> common_ecs::System<'a> for EventHandler<T> {
type SystemData = (
ReadExpect<'a, crate::metrics::ServerEventMetrics>,
WriteExpect<'a, EventBus<T>>,
T::SystemData<'a>,
);
const NAME: &'static str = T::NAME;
const ORIGIN: common_ecs::Origin = common_ecs::Origin::Server;
const PHASE: common_ecs::Phase = common_ecs::Phase::Apply;
fn run(_job: &mut common_ecs::Job<Self>, (metrics, mut ev, data): Self::SystemData) {
span!(guard, "Recv events");
let events = ev.recv_all_mut();
drop(guard);
span!(guard, "Add metrics for event");
metrics
.event_count
.with_label_values(&[T::NAME])
.inc_by(events.len() as u64);
drop(guard);
span!(guard, "Handle events");
T::handle(events, data);
drop(guard);
}
}
fn event_dispatch<T: ServerEvent>(builder: &mut DispatcherBuilder) {
// TODO: We currently don't consider the order of these event. But as
// some events produce other events that might be worth doing.
common_ecs::dispatch::<EventHandler<T>>(builder, &[]);
}
pub fn register_event_systems(builder: &mut DispatcherBuilder) {
inventory_manip::register_event_systems(builder);
entity_manipulation::register_event_systems(builder);
interaction::register_event_systems(builder);
invite::register_event_systems(builder);
}
pub enum Event { pub enum Event {
ClientConnected { ClientConnected {
entity: EcsEntity, entity: EcsEntity,
@ -62,281 +109,100 @@ pub enum Event {
} }
impl Server { impl Server {
pub fn handle_events(&mut self) -> Vec<Event> { fn handle_serial_events<T: Send + 'static, F: FnMut(&mut Self, T)>(&mut self, mut f: F) {
span!(_guard, "handle_events", "Server::handle_events"); if let Some(bus) = self.state.ecs_mut().get_mut::<EventBus<T>>() {
let mut frontend_events = Vec::new(); let events = bus.recv_all_mut();
let mut commands = Vec::new();
let mut chat_messages = Vec::new();
let events = self
.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.recv_all();
use strum::VariantNames;
let mut event_counts = vec![0u32; ServerEventDiscriminants::VARIANTS.len()];
for event in events {
// Count events by variant for metrics
event_counts[ServerEventDiscriminants::from(&event) as usize] += 1;
match event {
ServerEvent::Explosion {
pos,
explosion,
owner,
} => handle_explosion(self, pos, explosion, owner),
ServerEvent::Bonk { pos, owner, target } => handle_bonk(self, pos, owner, target),
ServerEvent::Shoot {
entity,
pos,
dir,
body,
light,
projectile,
speed,
object,
} => handle_shoot(
self, entity, pos, dir, body, light, projectile, speed, object,
),
ServerEvent::Shockwave {
properties,
pos,
ori,
} => handle_shockwave(self, properties, pos, ori),
ServerEvent::Knockback { entity, impulse } => {
handle_knockback(self, entity, impulse)
},
ServerEvent::HealthChange { entity, change } => {
handle_health_change(self, entity, change)
},
ServerEvent::PoiseChange { entity, change } => handle_poise(self, entity, change),
ServerEvent::Delete(entity) => handle_delete(self, entity),
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip),
ServerEvent::Respawn(entity) => handle_respawn(self, entity),
ServerEvent::LandOnGround {
entity,
vel,
surface_normal,
} => handle_land_on_ground(self, entity, vel, surface_normal),
ServerEvent::EnableLantern(entity) => handle_lantern(self, entity, true),
ServerEvent::DisableLantern(entity) => handle_lantern(self, entity, false),
ServerEvent::NpcInteract(interactor, target, subject) => {
handle_npc_interaction(self, interactor, target, subject)
},
ServerEvent::InitiateInvite(interactor, target, kind) => {
handle_invite(self, interactor, target, kind)
},
ServerEvent::InviteResponse(entity, response) => {
handle_invite_response(self, entity, response)
},
ServerEvent::ProcessTradeAction(entity, trade_id, action) => {
handle_process_trade_action(self, entity, trade_id, action);
},
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
ServerEvent::MountVolume(mounter, volume) => {
handle_mount_volume(self, mounter, volume)
},
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
ServerEvent::SetPetStay(command_giver, pet, stay) => {
handle_set_pet_stay(self, command_giver, pet, stay)
},
ServerEvent::Possess(possessor_uid, possesse_uid) => {
handle_possess(self, possessor_uid, possesse_uid)
},
ServerEvent::InitCharacterData {
entity,
character_id,
requested_view_distances,
} => handle_initialize_character(
self,
entity,
character_id,
requested_view_distances,
),
ServerEvent::InitSpectator(entity, requested_view_distances) => {
handle_initialize_spectator(self, entity, requested_view_distances)
},
ServerEvent::DeleteCharacter {
entity,
requesting_player_uuid,
character_id,
} => handle_character_delete(self, entity, requesting_player_uuid, character_id),
ServerEvent::UpdateCharacterData {
entity,
components,
metadata,
} => {
let (
body,
stats,
skill_set,
inventory,
waypoint,
pets,
active_abilities,
map_marker,
) = components;
let components = PersistedComponents {
body,
stats,
skill_set,
inventory,
waypoint,
pets,
active_abilities,
map_marker,
};
handle_loaded_character_data(self, entity, components, metadata);
},
ServerEvent::ExitIngame { entity } => {
handle_exit_ingame(self, entity, false);
},
ServerEvent::CreateNpc {
pos,
ori,
npc,
rider,
} => {
handle_create_npc(self, pos, ori, npc, rider);
},
ServerEvent::CreateShip {
pos,
ori,
ship,
rtsim_entity,
driver,
} => handle_create_ship(self, pos, ori, ship, rtsim_entity, driver, Vec::new()),
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
ServerEvent::CreateTeleporter(pos, portal) => {
handle_create_teleporter(self, pos, portal)
},
ServerEvent::ClientDisconnect(entity, reason) => {
frontend_events.push(handle_client_disconnect(self, entity, reason, false))
},
ServerEvent::ClientDisconnectWithoutPersistence(entity) => {
frontend_events.push(handle_client_disconnect(
self,
entity,
common::comp::DisconnectReason::Kicked,
true,
))
},
ServerEvent::Command(entity, name, args) => {
commands.push((entity, name, args));
},
ServerEvent::Chat(msg) => {
chat_messages.push(msg);
},
ServerEvent::Aura {
entity,
aura_change,
} => handle_aura(self, entity, aura_change),
ServerEvent::Buff {
entity,
buff_change,
} => handle_buff(self, entity, buff_change),
ServerEvent::EnergyChange { entity, change } => {
handle_energy_change(self, entity, change)
},
ServerEvent::ComboChange { entity, change } => {
handle_combo_change(self, entity, change)
},
ServerEvent::ParryHook {
defender,
attacker,
source,
} => handle_parry_hook(self, defender, attacker, source),
ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(self, entity, id),
ServerEvent::MineBlock { entity, pos, tool } => {
handle_mine_block(self, entity, pos, tool)
},
ServerEvent::TeleportTo {
entity,
target,
max_range,
} => handle_teleport_to(self, entity, target, max_range),
ServerEvent::CreateSafezone { range, pos } => {
self.state.create_safezone(range, pos).build();
},
ServerEvent::Sound { sound } => handle_sound(self, &sound),
ServerEvent::CreateSprite {
pos,
sprite,
del_timeout,
} => handle_create_sprite(self, pos, sprite, del_timeout),
ServerEvent::TamePet {
pet_entity,
owner_entity,
} => handle_tame_pet(self, pet_entity, owner_entity),
ServerEvent::EntityAttackedHook { entity, attacker } => {
handle_entity_attacked_hook(self, entity, attacker)
},
ServerEvent::ChangeAbility {
entity,
slot,
auxiliary_key,
new_ability,
} => handle_change_ability(self, entity, slot, auxiliary_key, new_ability),
ServerEvent::UpdateMapMarker { entity, update } => {
handle_update_map_marker(self, entity, update)
},
ServerEvent::MakeAdmin {
entity,
admin,
uuid,
} => handle_make_admin(self, entity, admin, uuid),
ServerEvent::ChangeStance { entity, stance } => {
handle_stance_change(self, entity, stance)
},
ServerEvent::ChangeBody { entity, new_body } => {
handle_change_body(self, entity, new_body)
},
ServerEvent::RemoveLightEmitter { entity } => {
handle_remove_light_emitter(self, entity)
},
ServerEvent::TeleportToPosition { entity, position } => {
handle_teleport_to_position(self, entity, position)
},
ServerEvent::StartTeleporting { entity, portal } => {
handle_start_teleporting(self, entity, portal)
},
ServerEvent::ToggleSpriteLight {
entity,
pos,
enable,
} => handle_toggle_sprite_light(self, entity, pos, enable),
}
}
{
let server_event_metrics = self let server_event_metrics = self
.state .state
.ecs() .ecs()
.read_resource::<crate::metrics::ServerEventMetrics>(); .read_resource::<crate::metrics::ServerEventMetrics>();
event_counts server_event_metrics
.into_iter() .event_count
.zip(ServerEventDiscriminants::VARIANTS) .with_label_values(&[std::any::type_name::<T>()])
.for_each(|(count, event_name)| { .inc_by(events.len() as u64);
server_event_metrics drop(server_event_metrics);
.event_count
.with_label_values(&[event_name])
.inc_by(count.into());
})
}
for (entity, name, args) in commands { for ev in events {
self.process_command(entity, name, args); f(self, ev)
}
} }
}
for msg in chat_messages { fn handle_all_serial_events(&mut self, frontend_events: &mut Vec<Event>) {
self.state.send_chat(msg); self.handle_serial_events(handle_initialize_character);
} self.handle_serial_events(handle_initialize_spectator);
self.handle_serial_events(handle_loaded_character_data);
self.handle_serial_events(|this, ev| {
handle_create_npc(this, ev);
});
self.handle_serial_events(handle_create_ship);
self.handle_serial_events(handle_shoot);
self.handle_serial_events(handle_shockwave);
self.handle_serial_events(handle_create_waypoint);
self.handle_serial_events(handle_create_teleporter);
self.handle_serial_events(handle_create_item_drop);
self.handle_serial_events(handle_create_object);
self.handle_serial_events(handle_delete);
self.handle_serial_events(handle_character_delete);
self.handle_serial_events(|this, ev: ExitIngameEvent| {
handle_exit_ingame(this, ev.entity, false)
});
self.handle_serial_events(|this, ev: ClientDisconnectEvent| {
handle_client_disconnect(this, ev.0, ev.1, false);
});
self.handle_serial_events(|this, ev: ClientDisconnectEvent| {
frontend_events.push(handle_client_disconnect(this, ev.0, ev.1, false));
});
self.handle_serial_events(|this, ev: ClientDisconnectWithoutPersistenceEvent| {
frontend_events.push(handle_client_disconnect(
this,
ev.0,
common::comp::DisconnectReason::Kicked,
true,
));
});
self.handle_serial_events(handle_possess);
self.handle_serial_events(|this, ev: CommandEvent| {
this.process_command(ev.0, ev.1, ev.2);
});
self.handle_serial_events(|this, ev: ChatEvent| {
this.state.send_chat(ev.0);
});
self.handle_serial_events(handle_mount);
self.handle_serial_events(handle_mount_volume);
self.handle_serial_events(handle_unmount);
self.handle_serial_events(handle_tame_pet);
self.handle_serial_events(handle_process_trade_action);
}
pub fn handle_events(&mut self) -> Vec<Event> {
let mut frontend_events = Vec::new();
span!(guard, "run event systems");
// This dispatches all the systems in parallel.
self.event_dispatcher.dispatch(self.state.ecs());
drop(guard);
span!(guard, "handle serial events");
self.handle_all_serial_events(&mut frontend_events);
drop(guard);
self.state.maintain_ecs();
frontend_events frontend_events
} }
pub fn create_event_dispatcher(pools: Arc<rayon::ThreadPool>) -> SendDispatcher<'static> {
span!(_guard, "create event dispatcher");
// Run systems to handle events.
// Create and run a dispatcher for ecs systems.
let mut dispatch_builder = DispatcherBuilder::new().with_pool(pools);
register_event_systems(&mut dispatch_builder);
dispatch_builder
.build()
.try_into_sendable()
.ok()
.expect("This should be sendable")
}
} }

View File

@ -0,0 +1,151 @@
use std::sync::Arc;
use common::{
comp::{self, pet::is_mountable},
consts::{MAX_MOUNT_RANGE, MAX_SPRITE_MOUNT_RANGE},
event::{MountEvent, MountVolumeEvent, UnmountEvent},
link::Is,
mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
rtsim::RtSimEntity,
uid::IdMaps,
};
use plugin_api::Uid;
use specs::WorldExt;
use crate::{rtsim::RtSim, state_ext::StateExt, Server};
pub fn within_mounting_range(
player_position: Option<&comp::Pos>,
mount_position: Option<&comp::Pos>,
) -> bool {
match (player_position, mount_position) {
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2),
_ => false,
}
}
pub fn handle_mount(server: &mut Server, MountEvent(rider, mount): MountEvent) {
let state = server.state_mut();
let within_range = {
let positions = state.ecs().read_storage::<comp::Pos>();
within_mounting_range(positions.get(rider), positions.get(mount))
};
if within_range {
let uids = state.ecs().read_storage::<Uid>();
if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied())
{
let is_pet_of = |mount, rider_uid| {
matches!(
state
.ecs()
.read_storage::<comp::Alignment>()
.get(mount),
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
)
};
let can_ride = state
.ecs()
.read_storage()
.get(mount)
.map_or(false, |mount_body| {
is_mountable(mount_body, state.ecs().read_storage().get(rider))
});
let is_stay = state
.ecs()
.read_storage::<comp::Agent>()
.get(mount)
.and_then(|x| x.stay_pos)
.is_some();
if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride && !is_stay
{
drop(uids);
let _ = state.link(Mounting {
mount: mount_uid,
rider: rider_uid,
});
}
}
}
}
pub fn handle_mount_volume(
server: &mut Server,
MountVolumeEvent(rider, volume_pos): MountVolumeEvent,
) {
let state = server.state_mut();
let block_transform = volume_pos.get_block_and_transform(
&state.terrain(),
&state.ecs().read_resource(),
|e| {
state
.read_storage()
.get(e)
.copied()
.zip(state.read_storage().get(e).copied())
},
&state.read_storage(),
);
if let Some((mat, _, block)) = block_transform
&& let Some(mount_offset) = block.mount_offset()
{
let mount_pos = (mat * mount_offset.0.with_w(1.0)).xyz();
let within_range = {
let positions = state.ecs().read_storage::<comp::Pos>();
positions.get(rider).map_or(false, |pos| {
pos.0.distance_squared(mount_pos) < MAX_SPRITE_MOUNT_RANGE.powi(2)
})
};
let maybe_uid = state.ecs().read_storage::<Uid>().get(rider).copied();
if let Some(rider) = maybe_uid
&& within_range
{
let _link_successful = state
.link(VolumeMounting {
pos: volume_pos,
block,
rider,
})
.is_ok();
#[cfg(feature = "worldgen")]
if _link_successful {
let uid_allocator = state.ecs().read_resource::<IdMaps>();
if let Some(rider_entity) = uid_allocator.uid_entity(rider)
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
let entity = uid_allocator.uid_entity(uid)?;
state.read_storage::<RtSimEntity>().get(entity).map(|v| v.0)
})
{
state
.ecs()
.write_resource::<RtSim>()
.hook_character_mount_volume(
&state.ecs().read_resource::<Arc<world::World>>(),
state
.ecs()
.read_resource::<world::IndexOwned>()
.as_index_ref(),
volume_pos,
rider_actor,
);
}
}
}
}
}
pub fn handle_unmount(server: &mut Server, UnmountEvent(rider): UnmountEvent) {
let state = server.state_mut();
state.ecs().write_storage::<Is<Rider>>().remove(rider);
state.ecs().write_storage::<Is<VolumeRider>>().remove(rider);
}

View File

@ -4,9 +4,9 @@ use crate::{
state_ext::StateExt, BattleModeBuffer, Server, state_ext::StateExt, BattleModeBuffer, Server,
}; };
use common::{ use common::{
character::CharacterId,
comp, comp,
comp::{group, pet::is_tameable, Presence, PresenceKind}, comp::{group, pet::is_tameable, Presence, PresenceKind},
event::{DeleteCharacterEvent, PossessEvent},
resources::Time, resources::Time,
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
}; };
@ -16,12 +16,7 @@ use common_state::State;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use tracing::{debug, error, trace, warn, Instrument}; use tracing::{debug, error, trace, warn, Instrument};
pub fn handle_character_delete( pub fn handle_character_delete(server: &mut Server, ev: DeleteCharacterEvent) {
server: &mut Server,
entity: EcsEntity,
requesting_player_uuid: String,
character_id: CharacterId,
) {
// Can't process a character delete for a player that has an in-game presence, // Can't process a character delete for a player that has an in-game presence,
// so kick them out before processing the delete. // so kick them out before processing the delete.
// NOTE: This relies on StateExt::handle_initialize_character adding the // NOTE: This relies on StateExt::handle_initialize_character adding the
@ -29,19 +24,19 @@ pub fn handle_character_delete(
// is in-game. // is in-game.
let has_presence = { let has_presence = {
let presences = server.state.ecs().read_storage::<Presence>(); let presences = server.state.ecs().read_storage::<Presence>();
presences.get(entity).is_some() presences.get(ev.entity).is_some()
}; };
if has_presence { if has_presence {
warn!( warn!(
?requesting_player_uuid, ?ev.requesting_player_uuid,
?character_id, ?ev.character_id,
"Character delete received while in-game, disconnecting client." "Character delete received while in-game, disconnecting client."
); );
handle_exit_ingame(server, entity, true); handle_exit_ingame(server, ev.entity, true);
} }
let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>(); let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>();
updater.queue_character_deletion(requesting_player_uuid, character_id); updater.queue_character_deletion(ev.requesting_player_uuid, ev.character_id);
} }
pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) { pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) {
@ -341,7 +336,10 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
/// FIXME: This code is dangerous and needs to be refactored. We can't just /// FIXME: This code is dangerous and needs to be refactored. We can't just
/// comment it out, but it needs to be fixed for a variety of reasons. Get rid /// comment it out, but it needs to be fixed for a variety of reasons. Get rid
/// of this ASAP! /// of this ASAP!
pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Uid) { pub fn handle_possess(
server: &mut Server,
PossessEvent(possessor_uid, possessee_uid): PossessEvent,
) {
use crate::presence::RegionSubscription; use crate::presence::RegionSubscription;
use common::{ use common::{
comp::{inventory::slot::EquipSlot, item, slot::Slot, Inventory}, comp::{inventory::slot::EquipSlot, item, slot::Slot, Inventory},

View File

@ -7,7 +7,8 @@ use common::{
Inventory, Inventory,
}, },
}, },
trade::{PendingTrade, ReducedInventory, TradeAction, TradeId, TradeResult, Trades}, event::ProcessTradeActionEvent,
trade::{PendingTrade, ReducedInventory, TradeAction, TradeResult, Trades},
}; };
use common_net::{ use common_net::{
msg::ServerGeneral, msg::ServerGeneral,
@ -19,8 +20,8 @@ use std::cmp::Ordering;
use tracing::{error, trace}; use tracing::{error, trace};
use world::IndexOwned; use world::IndexOwned;
fn notify_agent_simple( pub fn notify_agent_simple(
mut agents: specs::WriteStorage<Agent>, agents: &mut specs::WriteStorage<Agent>,
entity: EcsEntity, entity: EcsEntity,
event: AgentEvent, event: AgentEvent,
) { ) {
@ -55,9 +56,7 @@ fn notify_agent_prices(
/// Invoked when the trade UI is up, handling item changes, accepts, etc /// Invoked when the trade UI is up, handling item changes, accepts, etc
pub(super) fn handle_process_trade_action( pub(super) fn handle_process_trade_action(
server: &mut Server, server: &mut Server,
entity: EcsEntity, ProcessTradeActionEvent(entity, trade_id, action): ProcessTradeActionEvent,
trade_id: TradeId,
action: TradeAction,
) { ) {
if let Some(uid) = server.state.ecs().uid_from_entity(entity) { if let Some(uid) = server.state.ecs().uid_from_entity(entity) {
let mut trades = server.state.ecs().write_resource::<Trades>(); let mut trades = server.state.ecs().write_resource::<Trades>();
@ -68,7 +67,7 @@ pub(super) fn handle_process_trade_action(
.map(|e| { .map(|e| {
server.notify_client(e, ServerGeneral::FinishedTrade(TradeResult::Declined)); server.notify_client(e, ServerGeneral::FinishedTrade(TradeResult::Declined));
notify_agent_simple( notify_agent_simple(
server.state.ecs().write_storage::<Agent>(), &mut server.state.ecs().write_storage(),
e, e,
AgentEvent::FinishedTrade(TradeResult::Declined), AgentEvent::FinishedTrade(TradeResult::Declined),
); );
@ -95,7 +94,7 @@ pub(super) fn handle_process_trade_action(
if let Some(e) = server.state.ecs().entity_from_uid(*party) { if let Some(e) = server.state.ecs().entity_from_uid(*party) {
server.notify_client(e, ServerGeneral::FinishedTrade(result.clone())); server.notify_client(e, ServerGeneral::FinishedTrade(result.clone()));
notify_agent_simple( notify_agent_simple(
server.state.ecs().write_storage::<Agent>(), &mut server.state.ecs().write_storage(),
e, e,
AgentEvent::FinishedTrade(result.clone()), AgentEvent::FinishedTrade(result.clone()),
); );
@ -185,7 +184,7 @@ pub(crate) fn cancel_trades_for(state: &mut common_state::State, entity: EcsEnti
c.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined)); c.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
} }
notify_agent_simple( notify_agent_simple(
ecs.write_storage::<Agent>(), &mut ecs.write_storage::<Agent>(),
e, e,
AgentEvent::FinishedTrade(TradeResult::Declined), AgentEvent::FinishedTrade(TradeResult::Declined),
); );

View File

@ -5,7 +5,14 @@
clippy::needless_pass_by_ref_mut //until we find a better way for specs clippy::needless_pass_by_ref_mut //until we find a better way for specs
)] )]
#![deny(clippy::clone_on_ref_ptr)] #![deny(clippy::clone_on_ref_ptr)]
#![feature(box_patterns, let_chains, never_type, option_zip, unwrap_infallible)] #![feature(
box_patterns,
let_chains,
never_type,
option_zip,
unwrap_infallible,
const_type_name
)]
pub mod automod; pub mod automod;
mod character_creator; mod character_creator;
@ -72,7 +79,10 @@ use common::{
character::{CharacterId, CharacterItem}, character::{CharacterId, CharacterItem},
cmd::ServerChatCommand, cmd::ServerChatCommand,
comp, comp,
event::{EventBus, ServerEvent}, event::{
register_event_busses, ClientDisconnectEvent, ClientDisconnectWithoutPersistenceEvent,
EventBus, ExitIngameEvent, UpdateCharacterDataEvent,
},
link::Is, link::Is,
mounting::{Volume, VolumeRider}, mounting::{Volume, VolumeRider},
region::RegionMap, region::RegionMap,
@ -98,7 +108,9 @@ use persistence::{
character_updater::CharacterUpdater, character_updater::CharacterUpdater,
}; };
use prometheus::Registry; use prometheus::Registry;
use specs::{Builder, Entity as EcsEntity, Entity, Join, LendJoin, WorldExt}; use specs::{
shred::SendDispatcher, Builder, Entity as EcsEntity, Entity, Join, LendJoin, WorldExt,
};
use std::{ use std::{
i32, i32,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
@ -220,6 +232,7 @@ pub struct Server {
disconnect_all_clients_requested: bool, disconnect_all_clients_requested: bool,
server_constants: ServerConstants, server_constants: ServerConstants,
event_dispatcher: SendDispatcher<'static>,
} }
impl Server { impl Server {
@ -304,7 +317,7 @@ impl Server {
report_stage(ServerInitStage::StartingSystems); report_stage(ServerInitStage::StartingSystems);
let mut state = State::server( let mut state = State::server(
pools, Arc::clone(&pools),
world.sim().map_size_lg(), world.sim().map_size_lg(),
Arc::clone(&map.default_chunk), Arc::clone(&map.default_chunk),
|dispatcher_builder| { |dispatcher_builder| {
@ -318,13 +331,15 @@ impl Server {
} }
}, },
); );
register_event_busses(state.ecs_mut());
state.ecs_mut().insert(battlemode_buffer); state.ecs_mut().insert(battlemode_buffer);
state.ecs_mut().insert(settings.clone()); state.ecs_mut().insert(settings.clone());
state.ecs_mut().insert(editable_settings); state.ecs_mut().insert(editable_settings);
state.ecs_mut().insert(DataDir { state.ecs_mut().insert(DataDir {
path: data_dir.to_owned(), path: data_dir.to_owned(),
}); });
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
register_event_busses(state.ecs_mut());
state.ecs_mut().insert(Vec::<ChunkRequest>::new()); state.ecs_mut().insert(Vec::<ChunkRequest>::new());
state state
.ecs_mut() .ecs_mut()
@ -610,6 +625,8 @@ impl Server {
disconnect_all_clients_requested: false, disconnect_all_clients_requested: false,
server_constants, server_constants,
event_dispatcher: Self::create_event_dispatcher(pools),
}; };
debug!(?settings, "created veloren server with"); debug!(?settings, "created veloren server with");
@ -989,7 +1006,7 @@ impl Server {
), ),
}, },
CharacterScreenResponseKind::CharacterData(result) => { CharacterScreenResponseKind::CharacterData(result) => {
let message = match *result { match *result {
Ok((character_data, skill_set_persistence_load_error)) => { Ok((character_data, skill_set_persistence_load_error)) => {
let PersistedComponents { let PersistedComponents {
body, body,
@ -1013,11 +1030,11 @@ impl Server {
); );
// TODO: Does this need to be a server event? E.g. we could // TODO: Does this need to be a server event? E.g. we could
// just handle it here. // just handle it here.
ServerEvent::UpdateCharacterData { self.state.emit_event_now(UpdateCharacterDataEvent {
entity: response.target_entity, entity: response.target_entity,
components: character_data, components: character_data,
metadata: skill_set_persistence_load_error, metadata: skill_set_persistence_load_error,
} })
}, },
Err(error) => { Err(error) => {
// We failed to load data for the character from the DB. Notify // We failed to load data for the character from the DB. Notify
@ -1031,16 +1048,11 @@ impl Server {
); );
// Clean up the entity data on the server // Clean up the entity data on the server
ServerEvent::ExitIngame { self.state.emit_event_now(ExitIngameEvent {
entity: response.target_entity, entity: response.target_entity,
} })
}, },
}; }
self.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(message);
}, },
} }
}, },
@ -1194,15 +1206,15 @@ impl Server {
); );
for (_, entity) in (&clients, &entities).join() { for (_, entity) in (&clients, &entities).join() {
info!("Emitting client disconnect event for entity: {:?}", entity); info!("Emitting client disconnect event for entity: {:?}", entity);
let event = if with_persistence { if with_persistence {
ServerEvent::ClientDisconnect(entity, comp::DisconnectReason::Kicked) self.state.emit_event_now(ClientDisconnectEvent(
entity,
comp::DisconnectReason::Kicked,
))
} else { } else {
ServerEvent::ClientDisconnectWithoutPersistence(entity) self.state
.emit_event_now(ClientDisconnectWithoutPersistenceEvent(entity))
}; };
self.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(event);
} }
self.disconnect_all_clients_requested = false; self.disconnect_all_clients_requested = false;

View File

@ -5,7 +5,7 @@ use crate::sys::terrain::NpcData;
use common::{ use common::{
calendar::Calendar, calendar::Calendar,
comp::{self, Agent, Body, Presence, PresenceKind}, comp::{self, Agent, Body, Presence, PresenceKind},
event::{EventBus, NpcBuilder, ServerEvent}, event::{CreateNpcEvent, CreateShipEvent, DeleteEvent, EventBus, NpcBuilder},
generation::{BodyBuilder, EntityConfig, EntityInfo}, generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Actor, NpcId, RtSimEntity}, rtsim::{Actor, NpcId, RtSimEntity},
@ -236,7 +236,9 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
Read<'a, Time>, Read<'a, Time>,
Read<'a, TimeOfDay>, Read<'a, TimeOfDay>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<CreateShipEvent>>,
Read<'a, EventBus<CreateNpcEvent>>,
Read<'a, EventBus<DeleteEvent>>,
WriteExpect<'a, RtSim>, WriteExpect<'a, RtSim>,
ReadExpect<'a, Arc<world::World>>, ReadExpect<'a, Arc<world::World>>,
ReadExpect<'a, world::IndexOwned>, ReadExpect<'a, world::IndexOwned>,
@ -259,7 +261,9 @@ impl<'a> System<'a> for Sys {
dt, dt,
time, time,
time_of_day, time_of_day,
server_event_bus, create_ship_events,
create_npc_events,
delete_events,
mut rtsim, mut rtsim,
world, world,
index, index,
@ -271,7 +275,9 @@ impl<'a> System<'a> for Sys {
calendar, calendar,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut emitter = server_event_bus.emitter(); let mut create_ship_emitter = create_ship_events.emitter();
let mut create_npc_emitter = create_npc_events.emitter();
let mut delete_emitter = delete_events.emitter();
let rtsim = &mut *rtsim; let rtsim = &mut *rtsim;
let calendar_data = (*time_of_day, (*calendar).clone()); let calendar_data = (*time_of_day, (*calendar).clone());
@ -317,7 +323,7 @@ impl<'a> System<'a> for Sys {
let mut create_event = |id: NpcId, npc: &Npc, steering: Option<NpcBuilder>| match npc.body { let mut create_event = |id: NpcId, npc: &Npc, steering: Option<NpcBuilder>| match npc.body {
Body::Ship(body) => { Body::Ship(body) => {
emitter.emit(ServerEvent::CreateShip { create_ship_emitter.emit(CreateShipEvent {
pos: comp::Pos(npc.wpos), pos: comp::Pos(npc.wpos),
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))), ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
ship: body, ship: body,
@ -333,7 +339,7 @@ impl<'a> System<'a> for Sys {
Some(&calendar_data), Some(&calendar_data),
); );
emitter.emit(match NpcData::from_entity_info(entity_info) { create_npc_emitter.emit(match NpcData::from_entity_info(entity_info) {
NpcData::Data { NpcData::Data {
pos, pos,
stats, stats,
@ -346,7 +352,7 @@ impl<'a> System<'a> for Sys {
alignment, alignment,
scale, scale,
loot, loot,
} => ServerEvent::CreateNpc { } => CreateNpcEvent {
pos, pos,
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))), ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
npc: NpcBuilder::new(stats, body, alignment) npc: NpcBuilder::new(stats, body, alignment)
@ -458,7 +464,6 @@ impl<'a> System<'a> for Sys {
} }
} }
let mut emitter = server_event_bus.emitter();
// Synchronise rtsim NPC with entity data // Synchronise rtsim NPC with entity data
for (entity, pos, rtsim_entity, agent) in ( for (entity, pos, rtsim_entity, agent) in (
&entities, &entities,
@ -489,7 +494,7 @@ impl<'a> System<'a> for Sys {
} }
}, },
SimulationMode::Simulated => { SimulationMode::Simulated => {
emitter.emit(ServerEvent::Delete(entity)); delete_emitter.emit(DeleteEvent(entity));
}, },
} }
} }

View File

@ -42,7 +42,10 @@ use common_net::{
}; };
use common_state::State; use common_state::State;
use rand::prelude::*; use rand::prelude::*;
use specs::{Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt}; use specs::{
storage::{GenericReadStorage, GenericWriteStorage},
Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt, WriteStorage,
};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tracing::{error, trace, warn}; use tracing::{error, trace, warn};
use vek::*; use vek::*;
@ -69,6 +72,7 @@ pub trait StateExt {
fn create_item_drop( fn create_item_drop(
&mut self, &mut self,
pos: comp::Pos, pos: comp::Pos,
ori: comp::Ori,
vel: comp::Vel, vel: comp::Vel,
item: Item, item: Item,
loot_owner: Option<LootOwner>, loot_owner: Option<LootOwner>,
@ -335,6 +339,7 @@ impl StateExt for State {
fn create_item_drop( fn create_item_drop(
&mut self, &mut self,
pos: comp::Pos, pos: comp::Pos,
ori: comp::Ori,
vel: comp::Vel, vel: comp::Vel,
item: Item, item: Item,
loot_owner: Option<LootOwner>, loot_owner: Option<LootOwner>,
@ -397,6 +402,7 @@ impl StateExt for State {
.create_entity_synced() .create_entity_synced()
.with(item) .with(item)
.with(pos) .with(pos)
.with(ori)
.with(vel) .with(vel)
.with(item_drop.orientation(&mut thread_rng())) .with(item_drop.orientation(&mut thread_rng()))
.with(item_drop.mass()) .with(item_drop.mass())
@ -1253,72 +1259,85 @@ impl StateExt for State {
dismount_volume: bool, dismount_volume: bool,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T, f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> Result<T, Content> { ) -> Result<T, Content> {
if dismount_volume { let ecs = self.ecs_mut();
self.ecs().write_storage::<Is<VolumeRider>>().remove(entity); position_mut(
} entity,
dismount_volume,
let entity = self f,
.read_storage::<Is<Rider>>() &ecs.read_resource(),
.get(entity) &mut ecs.write_storage(),
.and_then(|is_rider| { ecs.write_storage(),
self.ecs() ecs.write_storage(),
.read_resource::<IdMaps>() ecs.read_storage(),
.uid_entity(is_rider.mount) ecs.read_storage(),
}) ecs.read_storage(),
.map(Ok) )
.or_else(|| {
self.read_storage::<Is<VolumeRider>>()
.get(entity)
.and_then(|volume_rider| {
Some(match volume_rider.pos.kind {
common::mounting::Volume::Terrain => Err("Tried to move the world."),
common::mounting::Volume::Entity(uid) => {
Ok(self.ecs().read_resource::<IdMaps>().uid_entity(uid)?)
},
})
})
})
.unwrap_or(Ok(entity))?;
let mut maybe_pos = None;
let res = self
.ecs()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| {
let res = f(pos);
maybe_pos = Some(pos.0);
res
})
.ok_or(Content::localized_with_args(
"command-position-unavailable",
[("target", "entity")],
));
if let Some(pos) = maybe_pos {
if self
.ecs()
.read_storage::<Presence>()
.get(entity)
.map(|presence| presence.kind == PresenceKind::Spectator)
.unwrap_or(false)
{
self.read_storage::<Client>().get(entity).map(|client| {
client.send_fallible(ServerGeneral::SpectatePosition(pos));
});
} else {
self.ecs()
.write_storage::<comp::ForceUpdate>()
.get_mut(entity)
.map(|force_update| force_update.update());
}
}
res
} }
} }
pub fn position_mut<T>(
entity: EcsEntity,
dismount_volume: bool,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
id_maps: &IdMaps,
is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
mut positions: impl GenericWriteStorage<Component = comp::Pos>,
mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
is_riders: impl GenericReadStorage<Component = Is<Rider>>,
presences: impl GenericReadStorage<Component = Presence>,
clients: impl GenericReadStorage<Component = Client>,
) -> Result<T, Content> {
if dismount_volume {
is_volume_riders.remove(entity);
}
let entity = is_riders
.get(entity)
.and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
.map(Ok)
.or_else(|| {
is_volume_riders.get(entity).and_then(|volume_rider| {
Some(match volume_rider.pos.kind {
common::mounting::Volume::Terrain => Err("Tried to move the world."),
common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
})
})
})
.unwrap_or(Ok(entity))?;
let mut maybe_pos = None;
let res = positions
.get_mut(entity)
.map(|pos| {
let res = f(pos);
maybe_pos = Some(pos.0);
res
})
.ok_or(Content::localized_with_args(
"command-position-unavailable",
[("target", "entity")],
));
if let Some(pos) = maybe_pos {
if presences
.get(entity)
.map(|presence| presence.kind == PresenceKind::Spectator)
.unwrap_or(false)
{
clients.get(entity).map(|client| {
client.send_fallible(ServerGeneral::SpectatePosition(pos));
});
} else {
force_updates
.get_mut(entity)
.map(|force_update| force_update.update());
}
}
res
}
fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) { fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() { for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
if g == group { if g == group {

View File

@ -1,4 +1,5 @@
pub mod behavior_tree; pub mod behavior_tree;
use server_agent::data::AgentEvents;
pub use server_agent::{action_nodes, attack, consts, data, util}; pub use server_agent::{action_nodes, attack, consts, data, util};
use vek::Vec3; use vek::Vec3;
@ -11,7 +12,6 @@ use common::{
self, inventory::slot::EquipSlot, item::ItemDesc, Agent, Alignment, Body, CharacterState, self, inventory::slot::EquipSlot, item::ItemDesc, Agent, Alignment, Body, CharacterState,
Controller, Health, InputKind, Scale, Controller, Health, InputKind, Scale,
}, },
event::{EventBus, ServerEvent},
mounting::Volume, mounting::Volume,
path::TraversalConfig, path::TraversalConfig,
}; };
@ -19,7 +19,7 @@ use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System}; use common_ecs::{Job, Origin, ParMode, Phase, System};
use rand::thread_rng; use rand::thread_rng;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{LendJoin, ParJoin, Read, WriteStorage}; use specs::{LendJoin, ParJoin, WriteStorage};
/// This system will allow NPCs to modify their controller /// This system will allow NPCs to modify their controller
#[derive(Default)] #[derive(Default)]
@ -27,7 +27,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
ReadData<'a>, ReadData<'a>,
Read<'a, EventBus<ServerEvent>>, AgentEvents<'a>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
); );
@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
job: &mut Job<Self>, job: &mut Job<Self>,
(read_data, event_bus, mut agents, mut controllers): Self::SystemData, (read_data, events, mut agents, mut controllers): Self::SystemData,
) { ) {
job.cpu_stats.measure(ParMode::Rayon); job.cpu_stats.measure(ParMode::Rayon);
@ -97,7 +97,7 @@ impl<'a> System<'a> for Sys {
rtsim_entity, rtsim_entity,
(_, is_rider, is_volume_rider), (_, is_rider, is_volume_rider),
)| { )| {
let mut event_emitter = event_bus.emitter(); let mut emitters = events.get_emitters();
let mut rng = thread_rng(); let mut rng = thread_rng();
// The entity that is moving, if riding it's the mount, otherwise it's itself // The entity that is moving, if riding it's the mount, otherwise it's itself
@ -263,7 +263,7 @@ impl<'a> System<'a> for Sys {
agent, agent,
agent_data: data, agent_data: data,
read_data: &read_data, read_data: &read_data,
event_emitter: &mut event_emitter, emitters: &mut emitters,
controller, controller,
rng: &mut rng, rng: &mut rng,
}; };

View File

@ -8,11 +8,11 @@ use common::{
Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, ControlAction, Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, ControlAction,
ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind, ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind,
}, },
event::{Emitter, ServerEvent},
path::TraversalConfig, path::TraversalConfig,
rtsim::{NpcAction, RtSimEntity}, rtsim::{NpcAction, RtSimEntity},
}; };
use rand::{prelude::ThreadRng, thread_rng, Rng}; use rand::{prelude::ThreadRng, thread_rng, Rng};
use server_agent::data::AgentEmitters;
use specs::Entity as EcsEntity; use specs::Entity as EcsEntity;
use vek::{Vec2, Vec3}; use vek::{Vec2, Vec3};
@ -39,7 +39,7 @@ pub struct BehaviorData<'a, 'b, 'c> {
pub agent: &'a mut Agent, pub agent: &'a mut Agent,
pub agent_data: AgentData<'a>, pub agent_data: AgentData<'a>,
pub read_data: &'a ReadData<'a>, pub read_data: &'a ReadData<'a>,
pub event_emitter: &'a mut Emitter<'c, ServerEvent>, pub emitters: &'a mut AgentEmitters<'c>,
pub controller: &'a mut Controller, pub controller: &'a mut Controller,
pub rng: &'b mut ThreadRng, pub rng: &'b mut ThreadRng,
} }
@ -454,7 +454,7 @@ fn attack_if_owner_hurt(bdata: &mut BehaviorData) -> bool {
bdata.agent, bdata.agent,
bdata.read_data, bdata.read_data,
bdata.controller, bdata.controller,
bdata.event_emitter, bdata.emitters,
bdata.rng, bdata.rng,
); );
return true; return true;
@ -519,7 +519,7 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
} }
} }
bdata.controller.push_utterance(UtteranceKind::Greeting); bdata.controller.push_utterance(UtteranceKind::Greeting);
bdata.agent_data.chat_npc(msg, bdata.event_emitter); bdata.agent_data.chat_npc(msg, bdata.emitters);
} }
}, },
NpcAction::Attack(target) => { NpcAction::Attack(target) => {
@ -574,7 +574,7 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
bdata.agent, bdata.agent,
bdata.controller, bdata.controller,
bdata.read_data, bdata.read_data,
bdata.event_emitter, bdata.emitters,
AgentData::is_enemy, AgentData::is_enemy,
); );
} else { } else {
@ -582,7 +582,7 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
bdata.agent, bdata.agent,
bdata.controller, bdata.controller,
bdata.read_data, bdata.read_data,
bdata.event_emitter, bdata.emitters,
bdata.rng, bdata.rng,
); );
} }
@ -728,7 +728,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
agent, agent,
agent_data, agent_data,
read_data, read_data,
event_emitter, emitters,
controller, controller,
rng, rng,
} = bdata; } = bdata;
@ -777,7 +777,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
== 0.0 == 0.0
{ {
agent_data.cry_out(agent, event_emitter, read_data); agent_data.cry_out(agent, emitters, read_data);
agent.behavior_state.timers agent.behavior_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01; [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
agent.flee_from_pos = { agent.flee_from_pos = {
@ -803,12 +803,12 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.0; [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.0;
agent.target = None; agent.target = None;
agent.flee_from_pos = None; agent.flee_from_pos = None;
agent_data.idle(agent, controller, read_data, event_emitter, rng); agent_data.idle(agent, controller, read_data, emitters, rng);
} }
} else if is_dead(target, read_data) { } else if is_dead(target, read_data) {
agent_data.exclaim_relief_about_enemy_dead(agent, event_emitter); agent_data.exclaim_relief_about_enemy_dead(agent, emitters);
agent.target = None; agent.target = None;
agent_data.idle(agent, controller, read_data, event_emitter, rng); agent_data.idle(agent, controller, read_data, emitters, rng);
} else if is_invulnerable(target, read_data) } else if is_invulnerable(target, read_data)
|| stop_pursuing( || stop_pursuing(
dist_sqrd, dist_sqrd,
@ -820,7 +820,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
) )
{ {
agent.target = None; agent.target = None;
agent_data.idle(agent, controller, read_data, event_emitter, rng); agent_data.idle(agent, controller, read_data, emitters, rng);
} else { } else {
let is_time_to_retarget = let is_time_to_retarget =
read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS; read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
@ -830,7 +830,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
agent, agent,
controller, controller,
read_data, read_data,
event_emitter, emitters,
AgentData::is_enemy, AgentData::is_enemy,
); );
} }
@ -849,7 +849,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
controller, controller,
target, target,
read_data, read_data,
event_emitter, emitters,
rng, rng,
remembers_fight_with(agent_data.rtsim_entity, read_data, target), remembers_fight_with(agent_data.rtsim_entity, read_data, target),
); );

View File

@ -9,7 +9,7 @@ use common::{
BehaviorState, Content, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, BehaviorState, Content, ControlAction, Item, TradingBehavior, UnresolvedChatMsg,
UtteranceKind, UtteranceKind,
}, },
event::ServerEvent, event::{ChatEvent, EmitExt, ProcessTradeActionEvent},
rtsim::{Actor, NpcInput, PersonalityTrait}, rtsim::{Actor, NpcInput, PersonalityTrait},
trade::{TradeAction, TradePhase, TradeResult}, trade::{TradeAction, TradePhase, TradeResult},
}; };
@ -76,7 +76,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
agent, agent,
agent_data, agent_data,
read_data, read_data,
event_emitter, emitters,
controller, controller,
.. ..
} = bdata; } = bdata;
@ -171,7 +171,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
standard_response_msg() standard_response_msg()
}; };
// TODO: Localise // TODO: Localise
agent_data.chat_npc(Content::Plain(msg), event_emitter); agent_data.chat_npc(Content::Plain(msg), emitters);
} else { } else {
let mut rng = thread_rng(); let mut rng = thread_rng();
agent_data.chat_npc( agent_data.chat_npc(
@ -179,7 +179,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
.rtsim_controller .rtsim_controller
.personality .personality
.get_generic_comment(&mut rng), .get_generic_comment(&mut rng),
event_emitter, emitters,
); );
} }
} }
@ -191,13 +191,13 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_advertisement"), Content::localized("npc-speech-merchant_advertisement"),
agent, agent,
event_emitter, emitters,
); );
} else { } else {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_busy"), Content::localized("npc-speech-merchant_busy"),
agent, agent,
event_emitter, emitters,
); );
} }
} else { } else {
@ -206,7 +206,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_decline_trade"), Content::localized("npc-speech-villager_decline_trade"),
agent, agent,
event_emitter, emitters,
); );
} }
}, },
@ -224,7 +224,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
"{} ? I think it's {} {} from here!", "{} ? I think it's {} {} from here!",
location.name, dist, dir location.name, dist, dir
); );
agent_data.chat_npc(Content::Plain(msg), event_emitter); agent_data.chat_npc(Content::Plain(msg), emitters);
} }
}, },
Subject::Person(person) => { Subject::Person(person) => {
@ -259,7 +259,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
person.name() person.name()
) )
}; };
agent_data.chat_npc(Content::Plain(msg), event_emitter); agent_data.chat_npc(Content::Plain(msg), emitters);
} }
}, },
Subject::Work => {}, Subject::Work => {},
@ -276,7 +276,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
agent, agent,
agent_data, agent_data,
read_data, read_data,
event_emitter, emitters,
controller, controller,
.. ..
} = bdata; } = bdata;
@ -313,7 +313,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_busy"), Content::localized("npc-speech-merchant_busy"),
agent, agent,
event_emitter, emitters,
); );
} }
} else { } else {
@ -322,7 +322,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_decline_trade"), Content::localized("npc-speech-villager_decline_trade"),
agent, agent,
event_emitter, emitters,
); );
} }
} }
@ -364,7 +364,7 @@ pub fn handle_inbox_finished_trade(bdata: &mut BehaviorData) -> bool {
let BehaviorData { let BehaviorData {
agent, agent,
agent_data, agent_data,
event_emitter, emitters,
.. ..
} = bdata; } = bdata;
@ -379,14 +379,14 @@ pub fn handle_inbox_finished_trade(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_trade_successful"), Content::localized("npc-speech-merchant_trade_successful"),
agent, agent,
event_emitter, emitters,
); );
}, },
_ => { _ => {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_trade_declined"), Content::localized("npc-speech-merchant_trade_declined"),
agent, agent,
event_emitter, emitters,
); );
}, },
} }
@ -404,7 +404,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
agent, agent,
agent_data, agent_data,
read_data, read_data,
event_emitter, emitters,
.. ..
} = bdata; } = bdata;
@ -422,13 +422,13 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
.as_ref() .as_ref()
.and_then(|tgt_data| read_data.uids.get(tgt_data.target)) .and_then(|tgt_data| read_data.uids.get(tgt_data.target))
{ {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell( emitters.emit(ChatEvent(UnresolvedChatMsg::npc_tell(
*agent_data.uid, *agent_data.uid,
*with, *with,
content, content,
))); )));
} else { } else {
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say( emitters.emit(ChatEvent(UnresolvedChatMsg::npc_say(
*agent_data.uid, *agent_data.uid,
content, content,
))); )));
@ -457,7 +457,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
// the phase is included in the message, this shouldn't // the phase is included in the message, this shouldn't
// result in fully accepting an unfavourable trade)) // result in fully accepting an unfavourable trade))
if !pending.accept_flags[who] && !pending.is_empty_trade() { if !pending.accept_flags[who] && !pending.is_empty_trade() {
event_emitter.emit(ServerEvent::ProcessTradeAction( emitters.emit(ProcessTradeActionEvent(
*agent_data.entity, *agent_data.entity,
tradeid, tradeid,
TradeAction::Accept(pending.phase), TradeAction::Accept(pending.phase),
@ -482,7 +482,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
// decline // decline
agent.behavior.unset(BehaviorState::TRADING); agent.behavior.unset(BehaviorState::TRADING);
agent.target = None; agent.target = None;
event_emitter.emit(ServerEvent::ProcessTradeAction( emitters.emit(ProcessTradeActionEvent(
*agent_data.entity, *agent_data.entity,
tradeid, tradeid,
TradeAction::Decline, TradeAction::Decline,
@ -517,7 +517,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
&& !pending.offers[1 - who].is_empty() && !pending.offers[1 - who].is_empty()
&& only_food && only_food
{ {
event_emitter.emit(ServerEvent::ProcessTradeAction( emitters.emit(ProcessTradeActionEvent(
*agent_data.entity, *agent_data.entity,
tradeid, tradeid,
TradeAction::Accept(pending.phase), TradeAction::Accept(pending.phase),
@ -527,7 +527,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
TradingBehavior::None => { TradingBehavior::None => {
agent.behavior.unset(BehaviorState::TRADING); agent.behavior.unset(BehaviorState::TRADING);
agent.target = None; agent.target = None;
event_emitter.emit(ServerEvent::ProcessTradeAction( emitters.emit(ProcessTradeActionEvent(
*agent_data.entity, *agent_data.entity,
tradeid, tradeid,
TradeAction::Decline, TradeAction::Decline,
@ -549,7 +549,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
let BehaviorData { let BehaviorData {
agent, agent,
agent_data, agent_data,
event_emitter, emitters,
controller, controller,
.. ..
} = bdata; } = bdata;
@ -566,7 +566,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_busy"), Content::localized("npc-speech-villager_busy"),
agent, agent,
event_emitter, emitters,
); );
} }
}, },
@ -581,13 +581,13 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_busy"), Content::localized("npc-speech-merchant_busy"),
agent, agent,
event_emitter, emitters,
); );
} else { } else {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-villager_busy"), Content::localized("npc-speech-villager_busy"),
agent, agent,
event_emitter, emitters,
); );
} }
} }
@ -602,14 +602,14 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_trade_successful"), Content::localized("npc-speech-merchant_trade_successful"),
agent, agent,
event_emitter, emitters,
); );
}, },
_ => { _ => {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_trade_declined"), Content::localized("npc-speech-merchant_trade_declined"),
agent, agent,
event_emitter, emitters,
); );
}, },
} }
@ -622,7 +622,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
let (tradeid, _pending, _prices, _inventories) = &**boxval; let (tradeid, _pending, _prices, _inventories) = &**boxval;
agent.behavior.unset(BehaviorState::TRADING); agent.behavior.unset(BehaviorState::TRADING);
agent.target = None; agent.target = None;
event_emitter.emit(ServerEvent::ProcessTradeAction( emitters.emit(ProcessTradeActionEvent(
*agent_data.entity, *agent_data.entity,
*tradeid, *tradeid,
TradeAction::Decline, TradeAction::Decline,
@ -630,7 +630,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
agent_data.chat_npc_if_allowed_to_speak( agent_data.chat_npc_if_allowed_to_speak(
Content::localized("npc-speech-merchant_trade_cancelled_hostile"), Content::localized("npc-speech-merchant_trade_cancelled_hostile"),
agent, agent,
event_emitter, emitters,
); );
}, },
AgentEvent::ServerSound(_) | AgentEvent::Hurt => return false, AgentEvent::ServerSound(_) | AgentEvent::Hurt => return false,

View File

@ -12,21 +12,35 @@ use crate::{
}; };
use common::{ use common::{
comp::{Admin, AdminRole, ChatType, Player, Presence, Waypoint}, comp::{Admin, AdminRole, ChatType, Player, Presence, Waypoint},
event::{EventBus, ServerEvent}, event::{
ChatEvent, ClientDisconnectEvent, DeleteCharacterEvent, EmitExt, InitializeCharacterEvent,
InitializeSpectatorEvent,
},
event_emitters,
resources::Time, resources::Time,
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
uid::Uid, uid::Uid,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, ServerGeneral}; use common_net::msg::{ClientGeneral, ServerGeneral};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use specs::{Entities, Join, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::sync::{atomic::Ordering, Arc}; use std::sync::{atomic::Ordering, Arc};
use tracing::{debug, error}; use tracing::{debug, error};
event_emitters! {
struct Events[Emitters] {
init_spectator: InitializeSpectatorEvent,
init_character_data: InitializeCharacterEvent,
delete_character: DeleteCharacterEvent,
client_disconnect: ClientDisconnectEvent,
chat: ChatEvent,
}
}
impl Sys { impl Sys {
#[allow(clippy::too_many_arguments)] // Shhhh, go bother someone else clippy #[allow(clippy::too_many_arguments)] // Shhhh, go bother someone else clippy
fn handle_client_character_screen_msg( fn handle_client_character_screen_msg(
server_emitter: &mut common::event::Emitter<'_, ServerEvent>, emitters: &mut Emitters,
entity: specs::Entity, entity: specs::Entity,
client: &Client, client: &Client,
character_loader: &ReadExpect<'_, CharacterLoader>, character_loader: &ReadExpect<'_, CharacterLoader>,
@ -65,9 +79,7 @@ impl Sys {
if !client.login_msg_sent.load(Ordering::Relaxed) { if !client.login_msg_sent.load(Ordering::Relaxed) {
if let Some(player_uid) = uids.get(entity) { if let Some(player_uid) = uids.get(entity) {
server_emitter.emit(ServerEvent::Chat( emitters.emit(ChatEvent(ChatType::Online(*player_uid).into_plain_msg("")));
ChatType::Online(*player_uid).into_plain_msg(""),
));
client.login_msg_sent.store(true, Ordering::Relaxed); client.login_msg_sent.store(true, Ordering::Relaxed);
} }
@ -82,8 +94,7 @@ impl Sys {
{ {
send_join_messages()?; send_join_messages()?;
server_emitter emitters.emit(InitializeSpectatorEvent(entity, requested_view_distances));
.emit(ServerEvent::InitSpectator(entity, requested_view_distances));
} else { } else {
debug!("dropped Spectate msg from unprivileged client") debug!("dropped Spectate msg from unprivileged client")
} }
@ -131,7 +142,7 @@ impl Sys {
// Start inserting non-persisted/default components for the entity // Start inserting non-persisted/default components for the entity
// while we load the DB data // while we load the DB data
server_emitter.emit(ServerEvent::InitCharacterData { emitters.emit(InitializeCharacterEvent {
entity, entity,
character_id, character_id,
requested_view_distances, requested_view_distances,
@ -242,7 +253,7 @@ impl Sys {
}, },
ClientGeneral::DeleteCharacter(character_id) => { ClientGeneral::DeleteCharacter(character_id) => {
if let Some(player) = players.get(entity) { if let Some(player) = players.get(entity) {
server_emitter.emit(ServerEvent::DeleteCharacter { emitters.emit(DeleteCharacterEvent {
entity, entity,
requesting_player_uuid: player.uuid().to_string(), requesting_player_uuid: player.uuid().to_string(),
character_id, character_id,
@ -251,7 +262,7 @@ impl Sys {
}, },
_ => { _ => {
debug!("Kicking possibly misbehaving client due to invalid character request"); debug!("Kicking possibly misbehaving client due to invalid character request");
server_emitter.emit(ServerEvent::ClientDisconnect( emitters.emit(ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::NetworkError, common::comp::DisconnectReason::NetworkError,
)); ));
@ -267,7 +278,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Events<'a>,
ReadExpect<'a, CharacterLoader>, ReadExpect<'a, CharacterLoader>,
WriteExpect<'a, CharacterUpdater>, WriteExpect<'a, CharacterUpdater>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
@ -291,7 +302,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
entities, entities,
server_event_bus, events,
character_loader, character_loader,
mut character_updater, mut character_updater,
uids, uids,
@ -307,12 +318,12 @@ impl<'a> System<'a> for Sys {
world, world,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_event_bus.emitter(); let mut emitters = events.get_emitters();
for (entity, client) in (&entities, &mut clients).join() { for (entity, client) in (&entities, &mut clients).join() {
let _ = super::try_recv_all(client, 1, |client, msg| { let _ = super::try_recv_all(client, 1, |client, msg| {
Self::handle_client_character_screen_msg( Self::handle_client_character_screen_msg(
&mut server_emitter, &mut emitters,
entity, entity,
client, client,
&character_loader, &character_loader,

View File

@ -1,7 +1,8 @@
use crate::client::Client; use crate::client::Client;
use common::{ use common::{
comp::{ChatMode, ChatType, Content, Group, Player}, comp::{ChatMode, ChatType, Content, Group, Player},
event::{EventBus, ServerEvent}, event::{self, EmitExt},
event_emitters,
resources::ProgramTime, resources::ProgramTime,
uid::Uid, uid::Uid,
}; };
@ -11,9 +12,18 @@ use rayon::prelude::*;
use specs::{Entities, LendJoin, ParJoin, Read, ReadStorage, WriteStorage}; use specs::{Entities, LendJoin, ParJoin, Read, ReadStorage, WriteStorage};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
event_emitters! {
struct Events[Emitters] {
command: event::CommandEvent,
client_disconnect: event::ClientDisconnectEvent,
chat: event::ChatEvent,
}
}
impl Sys { impl Sys {
fn handle_general_msg( fn handle_general_msg(
server_emitter: &mut common::event::Emitter<'_, ServerEvent>, emitters: &mut Emitters,
entity: specs::Entity, entity: specs::Entity,
client: &Client, client: &Client,
player: Option<&Player>, player: Option<&Player>,
@ -35,7 +45,7 @@ impl Sys {
groups.get(entity).copied(), groups.get(entity).copied(),
) { ) {
Ok(message) => { Ok(message) => {
server_emitter.emit(ServerEvent::Chat(message)); emitters.emit(event::ChatEvent(message));
}, },
Err(error) => { Err(error) => {
client.send_fallible(ServerGeneral::ChatMsg( client.send_fallible(ServerGeneral::ChatMsg(
@ -52,19 +62,19 @@ impl Sys {
}, },
ClientGeneral::Command(name, args) => { ClientGeneral::Command(name, args) => {
if player.is_some() { if player.is_some() {
server_emitter.emit(ServerEvent::Command(entity, name, args)); emitters.emit(event::CommandEvent(entity, name, args));
} }
}, },
ClientGeneral::Terminate => { ClientGeneral::Terminate => {
debug!(?entity, "Client send message to terminate session"); debug!(?entity, "Client send message to terminate session");
server_emitter.emit(ServerEvent::ClientDisconnect( emitters.emit(event::ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::ClientRequested, common::comp::DisconnectReason::ClientRequested,
)); ));
}, },
_ => { _ => {
debug!("Kicking possible misbehaving client due to invalid message request"); debug!("Kicking possible misbehaving client due to invalid message request");
server_emitter.emit(ServerEvent::ClientDisconnect( emitters.emit(event::ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::NetworkError, common::comp::DisconnectReason::NetworkError,
)); ));
@ -80,7 +90,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Events<'a>,
Read<'a, ProgramTime>, Read<'a, ProgramTime>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, ChatMode>, ReadStorage<'a, ChatMode>,
@ -95,16 +105,16 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
(entities, server_event_bus, program_time, uids, chat_modes, players, groups, mut clients): Self::SystemData, (entities, events, program_time, uids, chat_modes, players, groups, mut clients): Self::SystemData,
) { ) {
(&entities, &mut clients, players.maybe()) (&entities, &mut clients, players.maybe())
.par_join() .par_join()
.for_each_init( .for_each_init(
|| server_event_bus.emitter(), || events.get_emitters(),
|server_emitter, (entity, client, player)| { |emitters, (entity, client, player)| {
let res = super::try_recv_all(client, 3, |client, msg| { let res = super::try_recv_all(client, 3, |client, msg| {
Self::handle_general_msg( Self::handle_general_msg(
server_emitter, emitters,
entity, entity,
client, client,
player, player,

View File

@ -6,7 +6,8 @@ use common::{
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
Pos, Presence, PresenceKind, SkillSet, Vel, Pos, Presence, PresenceKind, SkillSet, Vel,
}, },
event::{EventBus, ServerEvent}, event::{self, EmitExt},
event_emitters,
link::Is, link::Is,
mounting::{Rider, VolumeRider}, mounting::{Rider, VolumeRider},
resources::{DeltaTime, PlayerPhysicsSetting, PlayerPhysicsSettings}, resources::{DeltaTime, PlayerPhysicsSetting, PlayerPhysicsSettings},
@ -42,10 +43,19 @@ struct RareWrites<'a, 'b> {
_terrain_persistence: &'b mut TerrainPersistenceData<'a>, _terrain_persistence: &'b mut TerrainPersistenceData<'a>,
} }
event_emitters! {
struct Events[Emitters] {
exit_ingame: event::ExitIngameEvent,
request_site_info: event::RequestSiteInfoEvent,
update_map_marker: event::UpdateMapMarkerEvent,
client_disconnect: event::ClientDisconnectEvent,
}
}
impl Sys { impl Sys {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn handle_client_in_game_msg( fn handle_client_in_game_msg(
server_emitter: &mut common::event::Emitter<'_, ServerEvent>, emitters: &mut Emitters,
entity: specs::Entity, entity: specs::Entity,
client: &Client, client: &Client,
maybe_presence: &mut Option<&mut Presence>, maybe_presence: &mut Option<&mut Presence>,
@ -78,7 +88,7 @@ impl Sys {
match msg { match msg {
// Go back to registered state (char selection screen) // Go back to registered state (char selection screen)
ClientGeneral::ExitInGame => { ClientGeneral::ExitInGame => {
server_emitter.emit(ServerEvent::ExitIngame { entity }); emitters.emit(event::ExitIngameEvent { entity });
client.send(ServerGeneral::ExitInGameSuccess)?; client.send(ServerGeneral::ExitInGameSuccess)?;
*maybe_presence = None; *maybe_presence = None;
}, },
@ -212,7 +222,7 @@ impl Sys {
.transpose(); .transpose();
}, },
ClientGeneral::RequestSiteInfo(id) => { ClientGeneral::RequestSiteInfo(id) => {
server_emitter.emit(ServerEvent::RequestSiteInfo { entity, id }); emitters.emit(event::RequestSiteInfoEvent { entity, id });
}, },
ClientGeneral::RequestPlayerPhysics { ClientGeneral::RequestPlayerPhysics {
server_authoritative, server_authoritative,
@ -227,7 +237,7 @@ impl Sys {
presence.lossy_terrain_compression = lossy_terrain_compression; presence.lossy_terrain_compression = lossy_terrain_compression;
}, },
ClientGeneral::UpdateMapMarker(update) => { ClientGeneral::UpdateMapMarker(update) => {
server_emitter.emit(ServerEvent::UpdateMapMarker { entity, update }); emitters.emit(event::UpdateMapMarkerEvent { entity, update });
}, },
ClientGeneral::SpectatePosition(pos) => { ClientGeneral::SpectatePosition(pos) => {
if let Some(admin) = maybe_admin if let Some(admin) = maybe_admin
@ -251,7 +261,7 @@ impl Sys {
| ClientGeneral::Command(..) | ClientGeneral::Command(..)
| ClientGeneral::Terminate => { | ClientGeneral::Terminate => {
debug!("Kicking possibly misbehaving client due to invalid client in game request"); debug!("Kicking possibly misbehaving client due to invalid client in game request");
server_emitter.emit(ServerEvent::ClientDisconnect( emitters.emit(event::ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::NetworkError, common::comp::DisconnectReason::NetworkError,
)); ));
@ -268,7 +278,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Events<'a>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, SlowJobPool>, ReadExpect<'a, SlowJobPool>,
ReadStorage<'a, CanBuild>, ReadStorage<'a, CanBuild>,
@ -301,7 +311,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
entities, entities,
server_event_bus, events,
terrain, terrain,
slow_jobs, slow_jobs,
can_build, can_build,
@ -353,8 +363,8 @@ impl<'a> System<'a> for Sys {
// NOTE: Required because Specs has very poor work splitting for sparse joins. // NOTE: Required because Specs has very poor work splitting for sparse joins.
.par_bridge() .par_bridge()
.map_init( .map_init(
|| server_event_bus.emitter(), || events.get_emitters(),
|server_emitter, ( |emitters, (
entity, entity,
client, client,
mut maybe_presence, mut maybe_presence,
@ -382,7 +392,7 @@ impl<'a> System<'a> for Sys {
let mut player_physics = None; let mut player_physics = None;
let _ = super::try_recv_all(client, 2, |client, msg| { let _ = super::try_recv_all(client, 2, |client, msg| {
Self::handle_client_in_game_msg( Self::handle_client_in_game_msg(
server_emitter, emitters,
entity, entity,
client, client,
&mut clearable_maybe_presence, &mut clearable_maybe_presence,

View File

@ -1,6 +1,6 @@
use crate::{client::Client, Settings}; use crate::{client::Client, Settings};
use common::{ use common::{
event::{EventBus, ServerEvent}, event::{ClientDisconnectEvent, EventBus},
resources::ProgramTime, resources::ProgramTime,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
@ -25,7 +25,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ClientDisconnectEvent>>,
Read<'a, ProgramTime>, Read<'a, ProgramTime>,
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
Read<'a, Settings>, Read<'a, Settings>,
@ -37,11 +37,11 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
(entities, server_event_bus, program_time, mut clients, settings): Self::SystemData, (entities, client_disconnect, program_time, mut clients, settings): Self::SystemData,
) { ) {
(&entities, &mut clients).par_join().for_each_init( (&entities, &mut clients).par_join().for_each_init(
|| server_event_bus.emitter(), || client_disconnect.emitter(),
|server_emitter, (entity, client)| { |client_disconnect_emitter, (entity, client)| {
// ignore network events // ignore network events
while let Some(Ok(Some(_))) = while let Some(Ok(Some(_))) =
client.participant.as_mut().map(|p| p.try_fetch_event()) client.participant.as_mut().map(|p| p.try_fetch_event())
@ -52,7 +52,7 @@ impl<'a> System<'a> for Sys {
match res { match res {
Err(e) => { Err(e) => {
debug!(?entity, ?e, "network error with client, disconnecting"); debug!(?entity, ?e, "network error with client, disconnecting");
server_emitter.emit(ServerEvent::ClientDisconnect( client_disconnect_emitter.emit(ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::NetworkError, common::comp::DisconnectReason::NetworkError,
)); ));
@ -67,7 +67,7 @@ impl<'a> System<'a> for Sys {
// Timeout // Timeout
{ {
info!(?entity, "timeout error with client, disconnecting"); info!(?entity, "timeout error with client, disconnecting");
server_emitter.emit(ServerEvent::ClientDisconnect( client_disconnect_emitter.emit(ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::Timeout, common::comp::DisconnectReason::Timeout,
)); ));

View File

@ -7,7 +7,7 @@ use crate::{
}; };
use common::{ use common::{
comp::{self, Admin, Player, Stats}, comp::{self, Admin, Player, Stats},
event::{EventBus, ServerEvent}, event::{ClientDisconnectEvent, EventBus, MakeAdminEvent},
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book}, recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
resources::TimeOfDay, resources::TimeOfDay,
shared_server_config::ServerConstants, shared_server_config::ServerConstants,
@ -42,7 +42,8 @@ pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
server_event_bus: Read<'a, EventBus<ServerEvent>>, client_disconnect_events: Read<'a, EventBus<ClientDisconnectEvent>>,
make_admin_events: Read<'a, EventBus<MakeAdminEvent>>,
login_provider: ReadExpect<'a, LoginProvider>, login_provider: ReadExpect<'a, LoginProvider>,
player_metrics: ReadExpect<'a, PlayerMetrics>, player_metrics: ReadExpect<'a, PlayerMetrics>,
settings: ReadExpect<'a, Settings>, settings: ReadExpect<'a, Settings>,
@ -62,7 +63,6 @@ pub struct ReadData<'a> {
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Read<'a, EventBus<ServerEvent>>,
ReadData<'a>, ReadData<'a>,
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, Player>, WriteStorage<'a, Player>,
@ -75,8 +75,9 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
(event_bus, read_data, mut clients, mut players, mut pending_logins): Self::SystemData, (read_data, mut clients, mut players, mut pending_logins): Self::SystemData,
) { ) {
let mut make_admin_emitter = read_data.make_admin_events.emitter();
// Player list to send new players, and lookup from UUID to entity (so we don't // Player list to send new players, and lookup from UUID to entity (so we don't
// have to do a linear scan over all entities on each login to see if // have to do a linear scan over all entities on each login to see if
// it's a duplicate). // it's a duplicate).
@ -165,8 +166,8 @@ impl<'a> System<'a> for Sys {
// NOTE: Required because Specs has very poor work splitting for sparse joins. // NOTE: Required because Specs has very poor work splitting for sparse joins.
.par_bridge() .par_bridge()
.for_each_init( .for_each_init(
|| read_data.server_event_bus.emitter(), || read_data.client_disconnect_events.emitter(),
|server_emitter, (entity, uid, client, _, pending)| { |client_disconnect_emitter, (entity, uid, client, _, pending)| {
prof_span!("msg::register login"); prof_span!("msg::register login");
if let Err(e) = || -> Result<(), crate::error::Error> { if let Err(e) = || -> Result<(), crate::error::Error> {
let extra_checks = |username: String, uuid: authc::Uuid| { let extra_checks = |username: String, uuid: authc::Uuid| {
@ -238,7 +239,7 @@ impl<'a> System<'a> for Sys {
// NOTE: Done only on error to avoid doing extra work within // NOTE: Done only on error to avoid doing extra work within
// the lock. // the lock.
trace!(?e, "pending login returned error"); trace!(?e, "pending login returned error");
server_emitter.emit(ServerEvent::ClientDisconnect( client_disconnect_emitter.emit(ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::Kicked, common::comp::DisconnectReason::Kicked,
)); ));
@ -302,7 +303,7 @@ impl<'a> System<'a> for Sys {
); );
} }
// Remove old client // Remove old client
server_emitter.emit(ServerEvent::ClientDisconnect( client_disconnect_emitter.emit(ClientDisconnectEvent(
old_entity, old_entity,
common::comp::DisconnectReason::NewerLogin, common::comp::DisconnectReason::NewerLogin,
)); ));
@ -408,7 +409,7 @@ impl<'a> System<'a> for Sys {
if let Some(admin) = admin { if let Some(admin) = admin {
// We need to defer writing to the Admin storage since it's borrowed immutably // We need to defer writing to the Admin storage since it's borrowed immutably
// by this system via TrackedStorages. // by this system via TrackedStorages.
event_bus.emit_now(ServerEvent::MakeAdmin { make_admin_emitter.emit(MakeAdminEvent {
entity, entity,
admin: Admin(admin.role.into()), admin: Admin(admin.role.into()),
uuid, uuid,

View File

@ -4,7 +4,7 @@ use crate::{
}; };
use common::{ use common::{
comp::{Pos, Presence}, comp::{Pos, Presence},
event::{EventBus, ServerEvent}, event::{ClientDisconnectEvent, EventBus},
spiral::Spiral2d, spiral::Spiral2d,
terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid}, terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid},
vol::RectVolSize, vol::RectVolSize,
@ -21,8 +21,8 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ClientDisconnectEvent>>,
ReadExpect<'a, EventBus<ChunkSendEntry>>, Read<'a, EventBus<ChunkSendEntry>>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Lod>, ReadExpect<'a, Lod>,
ReadExpect<'a, NetworkRequestMetrics>, ReadExpect<'a, NetworkRequestMetrics>,
@ -40,7 +40,7 @@ impl<'a> System<'a> for Sys {
job: &mut Job<Self>, job: &mut Job<Self>,
( (
entities, entities,
server_event_bus, client_disconnect_events,
chunk_send_bus, chunk_send_bus,
terrain, terrain,
lod, lod,
@ -57,8 +57,8 @@ impl<'a> System<'a> for Sys {
// NOTE: Required because Specs has very poor work splitting for sparse joins. // NOTE: Required because Specs has very poor work splitting for sparse joins.
.par_bridge() .par_bridge()
.map_init( .map_init(
|| (chunk_send_bus.emitter(), server_event_bus.emitter()), || (chunk_send_bus.emitter(), client_disconnect_events.emitter()),
|(chunk_send_emitter, server_emitter), (entity, client, maybe_presence)| { |(chunk_send_emitter, client_disconnect_emitter), (entity, client, maybe_presence)| {
let mut chunk_requests = Vec::new(); let mut chunk_requests = Vec::new();
let _ = super::try_recv_all(client, 5, |client, msg| { let _ = super::try_recv_all(client, 5, |client, msg| {
// SPECIAL CASE: LOD zone requests can be sent by non-present players // SPECIAL CASE: LOD zone requests can be sent by non-present players
@ -112,7 +112,7 @@ impl<'a> System<'a> for Sys {
"Kicking possibly misbehaving client due to invalud terrain \ "Kicking possibly misbehaving client due to invalud terrain \
request" request"
); );
server_emitter.emit(ServerEvent::ClientDisconnect( client_disconnect_emitter.emit(ClientDisconnectEvent(
entity, entity,
common::comp::DisconnectReason::NetworkError, common::comp::DisconnectReason::NetworkError,
)); ));

View File

@ -2,7 +2,8 @@ use common::{
comp::{object, Body, Object, PhysicsState, Pos, Teleporting, Vel}, comp::{object, Body, Object, PhysicsState, Pos, Teleporting, Vel},
consts::TELEPORTER_RADIUS, consts::TELEPORTER_RADIUS,
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{ChangeBodyEvent, DeleteEvent, EmitExt, EventBus, ExplosionEvent, ShootEvent},
event_emitters,
outcome::Outcome, outcome::Outcome,
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, RadiusEffect, CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, RadiusEffect,
@ -11,15 +12,24 @@ use common_ecs::{Job, Origin, Phase, System};
use specs::{Entities, Join, LendJoin, Read, ReadStorage}; use specs::{Entities, Join, LendJoin, Read, ReadStorage};
use vek::Rgb; use vek::Rgb;
event_emitters! {
struct Events[Emitters] {
delete: DeleteEvent,
explosion: ExplosionEvent,
shoot: ShootEvent,
change_body: ChangeBodyEvent,
}
}
/// This system is responsible for handling misc object behaviours /// This system is responsible for handling misc object behaviours
#[derive(Default)] #[derive(Default)]
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Events<'a>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
Read<'a, Time>, Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, EventBus<Outcome>>, Read<'a, EventBus<Outcome>>,
Read<'a, CachedSpatialGrid>, Read<'a, CachedSpatialGrid>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
@ -38,9 +48,9 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
entities, entities,
events,
_dt, _dt,
time, time,
server_bus,
outcome_bus, outcome_bus,
spatial_grid, spatial_grid,
positions, positions,
@ -51,7 +61,7 @@ impl<'a> System<'a> for Sys {
teleporting, teleporting,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_bus.emitter(); let mut emitters = events.get_emitters();
// Objects // Objects
for (entity, pos, vel, physics, object, body) in ( for (entity, pos, vel, physics, object, body) in (
@ -67,8 +77,8 @@ impl<'a> System<'a> for Sys {
match object { match object {
Object::Bomb { owner } => { Object::Bomb { owner } => {
if physics.on_surface().is_some() { if physics.on_surface().is_some() {
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
server_emitter.emit(ServerEvent::Explosion { emitters.emit(ExplosionEvent {
pos: pos.0, pos: pos.0,
explosion: Explosion { explosion: Explosion {
effects: vec![ effects: vec![
@ -132,7 +142,7 @@ impl<'a> System<'a> for Sys {
phi.sin(), phi.sin(),
)) ))
.expect("nonzero vector should normalize"); .expect("nonzero vector should normalize");
server_emitter.emit(ServerEvent::Shoot { emitters.emit(ShootEvent {
entity, entity,
pos: *pos, pos: *pos,
dir, dir,
@ -160,8 +170,8 @@ impl<'a> System<'a> for Sys {
}); });
} }
} }
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
server_emitter.emit(ServerEvent::Explosion { emitters.emit(ExplosionEvent {
pos: pos.0, pos: pos.0,
explosion: Explosion { explosion: Explosion {
effects: vec![ effects: vec![
@ -186,7 +196,7 @@ impl<'a> System<'a> for Sys {
timeout, timeout,
} => { } => {
if (time.0 - spawned_at.0).max(0.0) > timeout.as_secs_f64() { if (time.0 - spawned_at.0).max(0.0) > timeout.as_secs_f64() {
server_emitter.emit(ServerEvent::Delete(entity)); emitters.emit(DeleteEvent(entity));
} }
}, },
Object::Portal { .. } => { Object::Portal { .. } => {
@ -204,7 +214,7 @@ impl<'a> System<'a> for Sys {
}); });
if (*body == Body::Object(object::Body::PortalActive)) != is_active { if (*body == Body::Object(object::Body::PortalActive)) != is_active {
server_bus.emit_now(ServerEvent::ChangeBody { emitters.emit(ChangeBodyEvent {
entity, entity,
new_body: Body::Object(if is_active { new_body: Body::Object(if is_active {
outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 }); outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 });

View File

@ -1,7 +1,7 @@
use common::{ use common::{
comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting}, comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
consts::TELEPORTER_RADIUS, consts::TELEPORTER_RADIUS,
event::{EventBus, ServerEvent}, event::{EventBus, TeleportToPositionEvent},
outcome::Outcome, outcome::Outcome,
resources::Time, resources::Time,
uid::Uid, uid::Uid,
@ -33,7 +33,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, CharacterState>, ReadStorage<'a, CharacterState>,
Read<'a, CachedSpatialGrid>, Read<'a, CachedSpatialGrid>,
Read<'a, Time>, Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<TeleportToPositionEvent>>,
Read<'a, EventBus<Outcome>>, Read<'a, EventBus<Outcome>>,
); );
@ -54,10 +54,12 @@ impl<'a> System<'a> for Sys {
character_states, character_states,
spatial_grid, spatial_grid,
time, time,
server_bus, teleport_to_position_events,
outcome_bus, outcome_bus,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut teleport_to_position_emitter = teleport_to_position_events.emitter();
let mut outcome_emitter = outcome_bus.emitter();
let check_aggro = |entity, pos: Vec3<f32>| { let check_aggro = |entity, pos: Vec3<f32>| {
spatial_grid spatial_grid
.0 .0
@ -126,11 +128,11 @@ impl<'a> System<'a> for Sys {
for entity in nearby { for entity in nearby {
cancel_teleporting.push(entity); cancel_teleporting.push(entity);
server_bus.emit_now(ServerEvent::TeleportToPosition { teleport_to_position_emitter.emit(TeleportToPositionEvent {
entity, entity,
position: *target, position: *target,
}); });
outcome_bus.emit_now(Outcome::TeleportedByPortal { pos: *target }); outcome_emitter.emit(Outcome::TeleportedByPortal { pos: *target });
} }
} }
} }

View File

@ -15,7 +15,10 @@ use common::{
self, agent, biped_small, bird_medium, misc::PortalData, skillset::skills, self, agent, biped_small, bird_medium, misc::PortalData, skillset::skills,
BehaviorCapability, ForceUpdate, Pos, Presence, Waypoint, BehaviorCapability, ForceUpdate, Pos, Presence, Waypoint,
}, },
event::{EventBus, NpcBuilder, ServerEvent}, event::{
CreateNpcEvent, CreateTeleporterEvent, CreateWaypointEvent, EmitExt, EventBus, NpcBuilder,
},
event_emitters,
generation::{EntityInfo, SpecialEntity}, generation::{EntityInfo, SpecialEntity},
lottery::LootSpec, lottery::LootSpec,
resources::{Time, TimeOfDay}, resources::{Time, TimeOfDay},
@ -51,6 +54,14 @@ type RtSimData<'a> = WriteExpect<'a, rtsim::RtSim>;
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
type RtSimData<'a> = (); type RtSimData<'a> = ();
event_emitters! {
struct Events[Emitters] {
create_npc: CreateNpcEvent,
create_waypoint: CreateWaypointEvent,
create_teleporter: CreateTeleporterEvent,
}
}
/// This system will handle loading generated chunks and unloading /// This system will handle loading generated chunks and unloading
/// unneeded chunks. /// unneeded chunks.
/// 1. Inserts newly generated chunks into the TerrainGrid /// 1. Inserts newly generated chunks into the TerrainGrid
@ -62,7 +73,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
Read<'a, EventBus<ServerEvent>>, Events<'a>,
Read<'a, Tick>, Read<'a, Tick>,
Read<'a, Settings>, Read<'a, Settings>,
Read<'a, TimeOfDay>, Read<'a, TimeOfDay>,
@ -94,7 +105,7 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
server_event_bus, events,
tick, tick,
server_settings, server_settings,
time_of_day, time_of_day,
@ -119,7 +130,7 @@ impl<'a> System<'a> for Sys {
time, time,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_event_bus.emitter(); let mut emitters = events.get_emitters();
// Generate requested chunks // Generate requested chunks
// //
@ -196,7 +207,7 @@ impl<'a> System<'a> for Sys {
let data = NpcData::from_entity_info(entity); let data = NpcData::from_entity_info(entity);
match data { match data {
NpcData::Waypoint(pos) => { NpcData::Waypoint(pos) => {
server_emitter.emit(ServerEvent::CreateWaypoint(pos)); emitters.emit(CreateWaypointEvent(pos));
}, },
NpcData::Data { NpcData::Data {
pos, pos,
@ -211,7 +222,7 @@ impl<'a> System<'a> for Sys {
scale, scale,
loot, loot,
} => { } => {
server_emitter.emit(ServerEvent::CreateNpc { emitters.emit(CreateNpcEvent {
pos, pos,
ori: comp::Ori::from(Dir::random_2d(&mut rng)), ori: comp::Ori::from(Dir::random_2d(&mut rng)),
npc: NpcBuilder::new(stats, body, alignment) npc: NpcBuilder::new(stats, body, alignment)
@ -227,7 +238,7 @@ impl<'a> System<'a> for Sys {
}); });
}, },
NpcData::Teleporter(pos, teleporter) => { NpcData::Teleporter(pos, teleporter) => {
server_emitter.emit(ServerEvent::CreateTeleporter(pos, teleporter)); emitters.emit(CreateTeleporterEvent(pos, teleporter));
}, },
} }
} }

View File

@ -1,7 +1,7 @@
use crate::wiring::{Circuit, WiringElement}; use crate::wiring::{Circuit, WiringElement};
use common::{ use common::{
comp::{LightEmitter, PhysicsState, Pos}, comp::{LightEmitter, PhysicsState, Pos},
event::{EventBus, ServerEvent}, event, event_emitters,
resources::EntitiesDiedLastTick, resources::EntitiesDiedLastTick,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
@ -20,13 +20,19 @@ pub struct ReadData<'a> {
entities_died_last_tick: Read<'a, EntitiesDiedLastTick>, entities_died_last_tick: Read<'a, EntitiesDiedLastTick>,
} }
event_emitters! {
struct Events[Emitters] {
shoot: event::ShootEvent,
}
}
/// This system is responsible for handling wiring (signals and wiring systems) /// This system is responsible for handling wiring (signals and wiring systems)
#[derive(Default)] #[derive(Default)]
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
ReadData<'a>, ReadData<'a>,
Read<'a, EventBus<ServerEvent>>, Events<'a>,
WriteStorage<'a, WiringElement>, WriteStorage<'a, WiringElement>,
WriteStorage<'a, LightEmitter>, // maybe WriteStorage<'a, LightEmitter>, // maybe
Write<'a, BlockChange>, Write<'a, BlockChange>,
@ -38,9 +44,9 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
(read_data, event_bus, mut wiring_elements, mut light_emitters, mut block_change): Self::SystemData, (read_data, events, mut wiring_elements, mut light_emitters, mut block_change): Self::SystemData,
) { ) {
let mut server_emitter = event_bus.emitter(); let mut emitters = events.get_emitters();
// Compute the output for each wiring element by computing // Compute the output for each wiring element by computing
// the output for each `OutputFormula` and store each value per output per // the output for each `OutputFormula` and store each value per output per
@ -128,7 +134,7 @@ impl<'a> System<'a> for Sys {
&wiring_element.inputs, &wiring_element.inputs,
physics_state, physics_state,
&read_data.entities_died_last_tick.0, &read_data.entities_died_last_tick.0,
&mut server_emitter, &mut emitters,
pos, pos,
&mut block_change, &mut block_change,
light_emitter.as_deref_mut(), light_emitter.as_deref_mut(),

View File

@ -56,7 +56,7 @@ impl<'a> System<'a> for Sys {
Write<'a, Option<WeatherJob>>, Write<'a, Option<WeatherJob>>,
WriteExpect<'a, WeatherGrid>, WriteExpect<'a, WeatherGrid>,
WriteExpect<'a, SlowJobPool>, WriteExpect<'a, SlowJobPool>,
ReadExpect<'a, EventBus<Outcome>>, Read<'a, EventBus<Outcome>>,
ReadExpect<'a, Arc<World>>, ReadExpect<'a, Arc<World>>,
ReadStorage<'a, Client>, ReadStorage<'a, Client>,
ReadStorage<'a, comp::Pos>, ReadStorage<'a, comp::Pos>,

View File

@ -1,6 +1,6 @@
use common::{ use common::{
comp::{item::tool, object, Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor}, comp::{item::tool, object, Body, LightEmitter, PhysicsState, Pos, ProjectileConstructor},
event::{Emitter, ServerEvent}, event::{EmitExt, ShootEvent},
terrain::{Block, TerrainChunkSize}, terrain::{Block, TerrainChunkSize},
util::Dir, util::Dir,
vol::RectVolSize, vol::RectVolSize,
@ -162,7 +162,7 @@ impl WiringAction {
inputs: &HashMap<String, f32>, inputs: &HashMap<String, f32>,
physics_state: Option<&PhysicsState>, physics_state: Option<&PhysicsState>,
entities_died_last_tick: &Vec<(Entity, Pos)>, entities_died_last_tick: &Vec<(Entity, Pos)>,
server_emitter: &mut Emitter<'_, ServerEvent>, emitters: &mut impl EmitExt<ShootEvent>,
pos: Option<&Pos>, pos: Option<&Pos>,
block_change: &mut BlockChange, block_change: &mut BlockChange,
mut light_emitter: Option<&mut LightEmitter>, mut light_emitter: Option<&mut LightEmitter>,
@ -183,7 +183,7 @@ impl WiringAction {
}, },
WiringActionEffect::SpawnProjectile { constr } => { WiringActionEffect::SpawnProjectile { constr } => {
if let Some(&pos) = pos { if let Some(&pos) = pos {
server_emitter.emit(ServerEvent::Shoot { emitters.emit(ShootEvent {
entity, entity,
pos, pos,
dir: Dir::forward(), dir: Dir::forward(),