initial setup and convert entity_manipulation

This commit is contained in:
Isse 2023-11-22 01:00:48 +01:00
parent d3a97ba34c
commit e651b9b2ac
64 changed files with 5499 additions and 4597 deletions

View File

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

View File

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

View File

@ -4,7 +4,8 @@ use crate::{
ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction,
Density, Energy, InputAttr, InputKind, Ori, Pos, Vel,
},
event::{LocalEvent, ServerEvent},
event::{self, EmitExt, LocalEvent},
event_emitters,
resources::Time,
states::{
self,
@ -34,19 +35,46 @@ pub struct StateUpdate {
pub character_activity: CharacterActivity,
}
pub struct OutputEvents<'a> {
local: &'a mut Vec<LocalEvent>,
server: &'a mut Vec<ServerEvent>,
event_emitters! {
pub struct CharacterStateEvents[CharacterStateEventEmitters] {
combo: event::ComboChangeEvent,
event: event::AuraEvent,
shoot: event::ShootEvent,
teleport_to: event::TeleportToEvent,
shockwave: event::ShockwaveEvent,
explosion: event::ExplosionEvent,
buff: event::BuffEvent,
inventory_manip: event::InventoryManipEvent,
sprite_summon: event::CreateSpriteEvent,
change_stance: event::ChangeStanceEvent,
create_npc: event::CreateNpcEvent,
energy_change: event::EnergyChangeEvent,
knockback: event::KnockbackEvent,
sprite_light: event::ToggleSpriteLightEvent,
}
}
impl<'a> OutputEvents<'a> {
pub fn new(local: &'a mut Vec<LocalEvent>, server: &'a mut Vec<ServerEvent>) -> Self {
pub struct OutputEvents<'a, 'b> {
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 }
}
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 {

View File

@ -217,6 +217,13 @@ impl<G> GenericChatMsg<G> {
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> {
let chat_type = match self.chat_type {
ChatType::Online(a) => ChatType::Online(a),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ use common::uid::IdMaps;
use common::{
calendar::Calendar,
comp,
event::{EventBus, LocalEvent, ServerEvent},
event::{register_event_busses, EventBus, LocalEvent},
link::Is,
mounting::{Mount, Rider, VolumeRider, VolumeRiders},
outcome::Outcome,
@ -331,7 +331,7 @@ impl State {
ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
// TODO: only register on the server
ecs.insert(EventBus::<ServerEvent>::default());
register_event_busses(&mut ecs);
ecs.insert(comp::group::GroupManager::default());
ecs.insert(SysMetrics::default());
ecs.insert(PhysicsMetrics::default());
@ -417,6 +417,15 @@ impl State {
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
/// component exists (this is already the behavior of functions like `fetch`
/// and `write_component_ignore_entity_dead`). Since all of our resources
@ -685,9 +694,7 @@ impl State {
self.dispatcher.dispatch(&self.ecs);
drop(guard);
section_span!(guard, "maintain ecs");
self.ecs.maintain();
drop(guard);
self.maintain_ecs();
if update_terrain {
self.apply_terrain_changes_internal(true, block_update);
@ -730,6 +737,11 @@ impl State {
drop(guard);
}
pub fn maintain_ecs(&mut self) {
span!(_guard, "maintain ecs");
self.ecs.maintain();
}
/// Clean up the state after a tick.
pub fn cleanup(&mut self) {
span!(_guard, "cleanup", "State::cleanup");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ use common::{
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent},
event::{EventBus, LandOnGroundEvent},
link::Is,
mounting::{Rider, VolumeRider},
outcome::Outcome,
@ -133,7 +133,7 @@ pub struct PhysicsRead<'a> {
uids: ReadStorage<'a, Uid>,
terrain: ReadExpect<'a, TerrainGrid>,
dt: Read<'a, DeltaTime>,
event_bus: Read<'a, EventBus<ServerEvent>>,
land_on_ground_event: Read<'a, EventBus<LandOnGroundEvent>>,
game_mode: ReadExpect<'a, GameMode>,
scales: ReadStorage<'a, Scale>,
stickies: ReadStorage<'a, Sticky>,
@ -1364,11 +1364,11 @@ impl<'a> PhysicsData<'a> {
}
drop(guard);
let mut event_emitter = read.event_bus.emitter();
let mut event_emitter = read.land_on_ground_event.emitter();
land_on_grounds
.into_iter()
.for_each(|(entity, vel, surface_normal)| {
event_emitter.emit(ServerEvent::LandOnGround {
event_emitter.emit(LandOnGroundEvent {
entity,
vel: vel.0,
surface_normal,

View File

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

View File

@ -6,7 +6,12 @@ use common::{
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
},
event::{EventBus, ServerEvent},
event::{
BuffEvent, ComboChangeEvent, DeleteEvent, EmitExt, EnergyChangeEvent,
EntityAttackedHookEvent, EventBus, HealthChangeEvent, KnockbackEvent, MineBlockEvent,
ParryHookEvent, PoiseChangeEvent, SoundEvent,
},
event_emitters,
outcome::Outcome,
resources::{DeltaTime, Time},
uid::{IdMaps, Uid},
@ -18,10 +23,26 @@ use rand::Rng;
use specs::{shred, Entities, Join, LendJoin, Read, ReadStorage, SystemData, WriteStorage};
use vek::*;
event_emitters! {
struct Events[Emitters] {
health_change: HealthChangeEvent,
energy_change: EnergyChangeEvent,
poise_change: PoiseChangeEvent,
sound: SoundEvent,
mine_block: MineBlockEvent,
parry_hook: ParryHookEvent,
kockback: KnockbackEvent,
entity_attack_hoow: EntityAttackedHookEvent,
combo_change: ComboChangeEvent,
buff: BuffEvent,
delete: DeleteEvent,
}
}
#[derive(SystemData)]
pub struct ReadData<'a> {
entities: Entities<'a>,
server_bus: Read<'a, EventBus<ServerEvent>>,
events: Events<'a>,
time: Read<'a, Time>,
players: ReadStorage<'a, Player>,
dt: Read<'a, DeltaTime>,
@ -63,7 +84,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>,
(read_data, mut shockwaves, mut shockwave_hit_lists, outcomes): Self::SystemData,
) {
let mut server_emitter = read_data.server_bus.emitter();
let mut emitters = read_data.events.get_emitters();
let mut outcomes_emitter = outcomes.emitter();
let mut rng = rand::thread_rng();
@ -93,7 +114,7 @@ impl<'a> System<'a> for Sys {
.and_then(|uid| read_data.id_maps.uid_entity(uid));
if rng.gen_bool(0.05) {
server_emitter.emit(ServerEvent::Sound {
emitters.emit(SoundEvent {
sound: Sound::new(SoundKind::Shockwave, pos.0, 40.0, time),
});
}
@ -101,7 +122,7 @@ impl<'a> System<'a> for Sys {
// If shockwave is out of time emit destroy event but still continue since it
// may have traveled and produced effects a bit before reaching it's end point
if time > end_time {
server_emitter.emit(ServerEvent::Delete(entity));
emitters.emit(DeleteEvent(entity));
continue;
}
@ -249,7 +270,7 @@ impl<'a> System<'a> for Sys {
1.0,
shockwave.dodgeable.to_attack_source(),
*read_data.time,
|e| server_emitter.emit(e),
&mut emitters,
|o| outcomes_emitter.emit(o),
&mut rng,
0,

View File

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

View File

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

View File

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

View File

@ -40,7 +40,10 @@ use common::{
},
depot,
effect::Effect,
event::{EventBus, ServerEvent},
event::{
ClientDisconnectEvent, CreateWaypointEvent, EventBus, ExplosionEvent, GroupManipEvent,
InitiateInviteEvent, TamePetEvent,
},
generation::{EntityConfig, EntityInfo},
link::Is,
mounting::{Rider, Volume, VolumeRider},
@ -1804,9 +1807,7 @@ fn handle_spawn(
// Add to group system if a pet
if matches!(alignment, comp::Alignment::Owned { .. }) {
let server_eventbus =
server.state.ecs().read_resource::<EventBus<ServerEvent>>();
server_eventbus.emit_now(ServerEvent::TamePet {
server.state.emit_event_now(TamePetEvent {
owner_entity: target,
pet_entity: new_entity,
});
@ -2079,8 +2080,8 @@ fn handle_spawn_campfire(
server
.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::CreateWaypoint(pos.0));
.read_resource::<EventBus<CreateWaypointEvent>>()
.emit_now(CreateWaypointEvent(pos.0));
server.notify_client(
client,
@ -2907,10 +2908,7 @@ fn handle_explosion(
.read_storage::<Uid>()
.get(target)
.copied();
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::Explosion {
server.state.emit_event_now(ExplosionEvent {
pos: pos.0,
explosion: Explosion {
effects: vec![
@ -3261,8 +3259,7 @@ fn handle_group_invite(
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::InitiateInvite(target, uid, InviteKind::Group));
.emit_event_now(InitiateInviteEvent(target, uid, InviteKind::Group));
if client != target {
server.notify_client(
@ -3301,8 +3298,7 @@ fn handle_group_kick(
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Kick(uid)));
.emit_event_now(GroupManipEvent(target, comp::GroupManip::Kick(uid)));
Ok(())
} else {
Err(Content::Plain(action.help_string()))
@ -3318,8 +3314,7 @@ fn handle_group_leave(
) -> CmdResult<()> {
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Leave));
.emit_event_now(GroupManipEvent(target, comp::GroupManip::Leave));
Ok(())
}
@ -3337,11 +3332,7 @@ fn handle_group_promote(
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::GroupManip(
target,
comp::GroupManip::AssignLeader(uid),
));
.emit_event_now(GroupManipEvent(target, comp::GroupManip::AssignLeader(uid)));
Ok(())
} else {
Err(Content::Plain(action.help_string()))
@ -3944,8 +3935,8 @@ fn kick_player(
);
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::ClientDisconnect(
.mut_resource::<EventBus<ClientDisconnectEvent>>()
.emit_now(ClientDisconnectEvent(
target_player,
comp::DisconnectReason::Kicked,
));

View File

@ -3,25 +3,24 @@ use crate::{
presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt,
};
use common::{
character::CharacterId,
comp::{
self,
aura::{Aura, AuraKind, AuraTarget},
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
misc::PortalData,
ship::figuredata::VOXEL_COLLIDER_MANIFEST,
shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos,
Projectile, TradingBehavior, Vel, WaypointArea,
Alignment, BehaviorCapability, ItemDrops, LightEmitter, Ori, Pos, TradingBehavior, Vel,
WaypointArea,
},
event::{
CreateNpcEvent, CreateShipEvent, CreateTeleporterEvent, CreateWaypointEvent, EventBus,
InitializeCharacterEvent, InitializeSpectatorEvent, ShockwaveEvent, ShootEvent,
UpdateCharacterDataEvent,
},
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
outcome::Outcome,
resources::{Secs, Time},
rtsim::RtSimEntity,
uid::{IdMaps, Uid},
util::Dir,
vol::IntoFullVolIterator,
ViewDistances,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use specs::{Builder, Entity as EcsEntity, WorldExt};
@ -29,56 +28,57 @@ use vek::{Rgb, Vec3};
use super::group_manip::update_map_markers;
pub fn handle_initialize_character(
server: &mut Server,
entity: EcsEntity,
character_id: CharacterId,
requested_view_distances: ViewDistances,
) {
pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) {
let updater = server.state.ecs().fetch::<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);
if !pending_database_action {
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
let clamped_vds = ev
.requested_view_distances
.clamp(server.settings().max_view_distance);
server
.state
.initialize_character_data(entity, character_id, clamped_vds);
.initialize_character_data(ev.entity, ev.character_id, clamped_vds);
// Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
if ev.requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(
ev.entity,
ServerGeneral::SetViewDistance(clamped_vds.terrain),
);
}
} else {
// A character delete or update was somehow initiated after the login commenced,
// so kick the client out of "ingame" without saving any data and abort
// the character loading process.
handle_exit_ingame(server, entity, true);
handle_exit_ingame(server, ev.entity, true);
}
}
pub fn handle_initialize_spectator(
server: &mut Server,
entity: EcsEntity,
requested_view_distances: ViewDistances,
) {
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
server.state.initialize_spectator_data(entity, clamped_vds);
pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) {
let clamped_vds = ev.1.clamp(server.settings().max_view_distance);
server.state.initialize_spectator_data(ev.0, clamped_vds);
// Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
if ev.1.terrain != clamped_vds.terrain {
server.notify_client(ev.0, ServerGeneral::SetViewDistance(clamped_vds.terrain));
}
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
sys::subscription::initialize_region_subscription(server.state.ecs(), ev.0);
}
pub fn handle_loaded_character_data(
server: &mut Server,
entity: EcsEntity,
loaded_components: PersistedComponents,
metadata: UpdateCharacterMetadata,
) {
pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) {
let loaded_components = PersistedComponents {
body: ev.components.0,
stats: ev.components.1,
skill_set: ev.components.2,
inventory: ev.components.3,
waypoint: ev.components.4,
pets: ev.components.5,
active_abilities: ev.components.6,
map_marker: ev.components.7,
};
if let Some(marker) = loaded_components.map_marker {
server.notify_client(
entity,
ev.entity,
ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
marker.0,
))),
@ -87,68 +87,62 @@ pub fn handle_loaded_character_data(
let result_msg = if let Err(err) = server
.state
.update_character_data(entity, loaded_components)
.update_character_data(ev.entity, loaded_components)
{
handle_exit_ingame(server, entity, false); // remove client from in-game state
handle_exit_ingame(server, ev.entity, false); // remove client from in-game state
ServerGeneral::CharacterDataLoadResult(Err(err))
} else {
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
sys::subscription::initialize_region_subscription(server.state.ecs(), ev.entity);
// We notify the client with the metadata result from the operation.
ServerGeneral::CharacterDataLoadResult(Ok(metadata))
ServerGeneral::CharacterDataLoadResult(Ok(ev.metadata))
};
server.notify_client(entity, result_msg);
server.notify_client(ev.entity, result_msg);
}
pub fn handle_create_npc(
server: &mut Server,
pos: Pos,
ori: Ori,
mut npc: NpcBuilder,
rider: Option<NpcBuilder>,
) -> EcsEntity {
pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEntity {
let entity = server
.state
.create_npc(
pos,
ori,
npc.stats,
npc.skill_set,
npc.health,
npc.poise,
npc.inventory,
npc.body,
ev.pos,
ev.ori,
ev.npc.stats,
ev.npc.skill_set,
ev.npc.health,
ev.npc.poise,
ev.npc.inventory,
ev.npc.body,
)
.with(npc.scale);
.with(ev.npc.scale);
if let Some(agent) = &mut npc.agent {
if let Alignment::Owned(_) = &npc.alignment {
if let Some(agent) = &mut ev.npc.agent {
if let Alignment::Owned(_) = &ev.npc.alignment {
agent.behavior.allow(BehaviorCapability::TRADE);
agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
}
}
let entity = entity.with(npc.alignment);
let entity = entity.with(ev.npc.alignment);
let entity = if let Some(agent) = npc.agent {
let entity = if let Some(agent) = ev.npc.agent {
entity.with(agent)
} else {
entity
};
let entity = if let Some(drop_items) = npc.loot.to_items() {
let entity = if let Some(drop_items) = ev.npc.loot.to_items() {
entity.with(ItemDrops(drop_items))
} else {
entity
};
let entity = if let Some(home_chunk) = npc.anchor {
let entity = if let Some(home_chunk) = ev.npc.anchor {
entity.with(home_chunk)
} else {
entity
};
// Rtsim entity added to IdMaps below.
let entity = if let Some(rtsim_entity) = npc.rtsim_entity {
let entity = if let Some(rtsim_entity) = ev.npc.rtsim_entity {
entity.with(rtsim_entity).with(RepositionOnChunkLoad {
needs_ground: false,
})
@ -156,7 +150,7 @@ pub fn handle_create_npc(
entity
};
let entity = if let Some(projectile) = npc.projectile {
let entity = if let Some(projectile) = ev.npc.projectile {
entity.with(projectile)
} else {
entity
@ -164,7 +158,7 @@ pub fn handle_create_npc(
let new_entity = entity.build();
if let Some(rtsim_entity) = npc.rtsim_entity {
if let Some(rtsim_entity) = ev.npc.rtsim_entity {
server
.state()
.ecs()
@ -173,7 +167,7 @@ pub fn handle_create_npc(
}
// Add to group system if a pet
if let comp::Alignment::Owned(owner_uid) = npc.alignment {
if let comp::Alignment::Owned(owner_uid) = ev.npc.alignment {
let state = server.state();
let uids = state.ecs().read_storage::<Uid>();
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::Passive => None,
Alignment::Enemy => Some(comp::group::ENEMY),
@ -214,8 +208,13 @@ pub fn handle_create_npc(
let _ = server.state.ecs().write_storage().insert(new_entity, group);
}
if let Some(rider) = rider {
let rider_entity = handle_create_npc(server, pos, Ori::default(), rider, None);
if let Some(rider) = ev.rider {
let rider_entity = handle_create_npc(server, CreateNpcEvent {
pos: ev.pos,
ori: Ori::default(),
npc: rider,
rider: None,
});
let uids = server.state().ecs().read_storage::<Uid>();
let link = Mounting {
mount: *uids.get(new_entity).expect("We just created this entity"),
@ -231,21 +230,13 @@ pub fn handle_create_npc(
new_entity
}
pub fn handle_create_ship(
server: &mut Server,
pos: Pos,
ori: Ori,
ship: comp::ship::Body,
rtsim_entity: Option<RtSimEntity>,
driver: Option<NpcBuilder>,
passengers: Vec<NpcBuilder>,
) {
let collider = ship.make_collider();
pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
let collider = ev.ship.make_collider();
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
// TODO: Find better solution for this, maybe something like a serverside block
// of interests.
let (mut steering, mut seats) = {
let (mut steering, mut _seats) = {
let mut steering = Vec::new();
let mut seats = Vec::new();
@ -263,7 +254,9 @@ pub fn handle_create_ship(
(steering.into_iter(), seats.into_iter())
};
let mut entity = server.state.create_ship(pos, ori, ship, |_| collider);
let mut entity = server
.state
.create_ship(ev.pos, ev.ori, ev.ship, |_| collider);
/*
if let Some(mut agent) = agent {
let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship));
@ -273,13 +266,18 @@ pub fn handle_create_ship(
entity = entity.with(agent);
}
*/
if let Some(rtsim_vehicle) = rtsim_entity {
if let Some(rtsim_vehicle) = ev.rtsim_entity {
entity = entity.with(rtsim_vehicle);
}
let entity = entity.build();
if let Some(driver) = driver {
let npc_entity = handle_create_npc(server, pos, ori, driver, None);
if let Some(driver) = ev.driver {
let npc_entity = handle_create_npc(server, CreateNpcEvent {
pos: ev.pos,
ori: ev.ori,
npc: driver,
rider: None,
});
let uids = server.state.ecs().read_storage::<Uid>();
let (rider_uid, mount_uid) = uids
@ -312,14 +310,14 @@ pub fn handle_create_ship(
}
}
for passenger in passengers {
let npc_entity = handle_create_npc(
server,
Pos(pos.0 + Vec3::unit_z() * 5.0),
ori,
passenger,
None,
);
/*
for passenger in ev.passengers {
let npc_entity = handle_create_npc(server, CreateNpcEvent {
pos: Pos(ev.pos.0 + Vec3::unit_z() * 5.0),
ori: ev.ori,
npc: passenger,
rider: None,
});
if let Some((rider_pos, rider_block)) = seats.next() {
let uids = server.state.ecs().read_storage::<Uid>();
let (rider_uid, mount_uid) = uids
@ -342,62 +340,54 @@ pub fn handle_create_ship(
.expect("Failed to link passanger to ship");
}
}
*/
}
pub fn handle_shoot(
server: &mut Server,
entity: EcsEntity,
pos: Pos,
dir: Dir,
body: Body,
light: Option<LightEmitter>,
projectile: Projectile,
speed: f32,
object: Option<Object>,
) {
pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
let state = server.state_mut();
let pos = pos.0;
let pos = ev.pos.0;
let vel = *dir * speed
let vel = *ev.dir * ev.speed
+ state
.ecs()
.read_storage::<Vel>()
.get(entity)
.get(ev.entity)
.map_or(Vec3::zero(), |v| v.0);
// Add an outcome
state
.ecs()
.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);
if let Some(light) = light {
let mut builder = state.create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile);
if let Some(light) = ev.light {
builder = builder.with(light)
}
if let Some(object) = object {
if let Some(object) = ev.object {
builder = builder.with(object)
}
builder.build();
}
pub fn handle_shockwave(
server: &mut Server,
properties: shockwave::Properties,
pos: Pos,
ori: Ori,
) {
pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
let state = server.state_mut();
state.create_shockwave(properties, pos, ori).build();
state
.create_shockwave(ev.properties, ev.pos, ev.ori)
.build();
}
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
pub fn handle_create_waypoint(server: &mut Server, ev: CreateWaypointEvent) {
let time = server.state.get_time();
server
.state
.create_object(Pos(pos), comp::object::Body::CampfireLit)
.create_object(Pos(ev.0), comp::object::Body::CampfireLit)
.with(LightEmitter {
col: Rgb::new(1.0, 0.3, 0.1),
strength: 5.0,
@ -435,9 +425,9 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
.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
.state
.create_teleporter(comp::Pos(pos), portal)
.create_teleporter(comp::Pos(ev.0), ev.1)
.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::{
comp::{
self,
@ -6,26 +6,24 @@ use common::{
invite::{InviteKind, PendingInvites},
ChatType, GroupManip,
},
uid::Uid,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use common_state::State;
use specs::{
world::{Entity, WorldExt},
ReadStorage, WriteStorage,
event::GroupManipEvent,
uid::{IdMaps, Uid},
};
use common_net::msg::ServerGeneral;
use specs::{world::Entity, Entities, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use super::ServerEvent;
pub fn can_invite(
state: &State,
clients: &ReadStorage<'_, Client>,
groups: &ReadStorage<'_, Group>,
group_manager: &GroupManager,
pending_invites: &mut WriteStorage<'_, PendingInvites>,
max_group_size: u32,
inviter: Entity,
invitee: Entity,
) -> bool {
// 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| {
group_manager
.group_info(*group)
@ -45,9 +43,7 @@ pub fn can_invite(
// Check if group max size is already reached
// Adding the current number of pending invites
let group_size_limit_reached = state
.ecs()
.read_storage()
let group_size_limit_reached = groups
.get(inviter)
.copied()
.and_then(|group| {
@ -112,21 +108,31 @@ pub fn update_map_markers<'a>(
}
}
// TODO: turn chat messages into enums
pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
impl ServerEvent for GroupManipEvent {
type SystemData<'a> = (
Entities<'a>,
WriteExpect<'a, GroupManager>,
ReadExpect<'a, IdMaps>,
WriteStorage<'a, Group>,
ReadStorage<'a, Client>,
ReadStorage<'a, Uid>,
ReadStorage<'a, comp::Alignment>,
ReadStorage<'a, comp::MapMarker>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(entities, mut group_manager, id_maps, mut groups, clients, uids, alignments, map_markers): Self::SystemData<'_>,
) {
for GroupManipEvent(entity, manip) in events {
match manip {
GroupManip::Leave => {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
group_manager.leave_group(
entity,
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&mut groups,
&alignments,
&uids,
&state.ecs().entities(),
&entities,
&mut |entity, group_change| {
clients
.get(entity)
@ -143,12 +149,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
);
},
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) {
let target = match id_maps.uid_entity(uid) {
Some(t) => t,
None => {
// Inform of failure
@ -184,9 +185,6 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
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)
@ -197,9 +195,9 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
group_manager.leave_group(
target,
&mut groups,
&state.ecs().read_storage(),
&alignments,
&uids,
&state.ecs().entities(),
&entities,
&mut |entity, group_change| {
clients
.get(entity)
@ -209,7 +207,12 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
update_map_markers(
&map_markers,
&uids,
c,
&group_change,
);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
@ -251,10 +254,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
}
},
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) {
let target = match id_maps.uid_entity(uid) {
Some(t) => t,
None => {
// Inform of failure
@ -267,9 +267,6 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
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)
@ -280,8 +277,8 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
group_manager.assign_leader(
target,
&groups,
&state.ecs().entities(),
&state.ecs().read_storage(),
&entities,
&alignments,
&uids,
|entity, group_change| {
clients
@ -292,7 +289,12 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
.map(|g| (g, c))
})
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
update_map_markers(
&map_markers,
&uids,
c,
&group_change,
);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
@ -317,7 +319,8 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
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.",
"Transfer failed: You are not the leader of the target's \
group.",
));
}
},
@ -334,3 +337,5 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
},
}
}
}
}

View File

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

View File

@ -1,64 +1,74 @@
use specs::{world::WorldExt, Entity as EcsEntity, Join};
use common_state::{BlockChange, ScheduledBlockChange};
use specs::{DispatcherBuilder, Join, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use vek::*;
use common::{
assets::{self, Concatenate},
comp::{
self,
agent::{AgentEvent, Sound, SoundKind},
dialogue::Subject,
agent::{AgentEvent, SoundKind},
inventory::slot::EquipSlot,
item::{flatten_counted_items, MaterialStatManifest},
loot_owner::LootOwnerKind,
pet::is_mountable,
tool::{AbilityMap, ToolKind},
Inventory, LootOwner, Pos, SkillGroupKind,
tool::AbilityMap,
},
consts::{
MAX_INTERACT_RANGE, MAX_MOUNT_RANGE, MAX_NPCINTERACT_RANGE, MAX_SPRITE_MOUNT_RANGE,
SOUND_TRAVEL_DIST_PER_VOLUME,
consts::{MAX_INTERACT_RANGE, MAX_NPCINTERACT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
event::{
CreateItemDropEvent, CreateSpriteEvent, EventBus, MineBlockEvent, NpcInteractEvent,
SetLanternEvent, SetPetStayEvent, SoundEvent, TamePetEvent, ToggleSpriteLightEvent,
},
event::EventBus,
link::Is,
mounting::{Mount, Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
mounting::Mount,
outcome::Outcome,
rtsim::RtSimEntity,
terrain::{Block, SpriteKind},
uid::{IdMaps, Uid},
terrain::{Block, SpriteKind, TerrainGrid},
uid::Uid,
util::Dir,
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 hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static;
use serde::Deserialize;
use std::{iter::FromIterator, sync::Arc};
use std::iter::FromIterator;
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
let ecs = server.state_mut().ecs();
use super::{event_dispatch, mounting::within_mounting_range, ServerEvent};
let lantern_exists = ecs
.read_storage::<comp::LightEmitter>()
pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
event_dispatch::<SetLanternEvent>(builder);
event_dispatch::<NpcInteractEvent>(builder);
event_dispatch::<SetPetStayEvent>(builder);
event_dispatch::<MineBlockEvent>(builder);
event_dispatch::<SoundEvent>(builder);
event_dispatch::<CreateSpriteEvent>(builder);
event_dispatch::<ToggleSpriteLightEvent>(builder);
}
impl ServerEvent for SetLanternEvent {
type SystemData<'a> = (
WriteStorage<'a, comp::LightEmitter>,
ReadStorage<'a, comp::Inventory>,
ReadStorage<'a, comp::Health>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut light_emitters, inventories, healths): Self::SystemData<'_>,
) {
for SetLanternEvent(entity, enable) in events {
let lantern_exists = light_emitters
.get(entity)
.map_or(false, |light| light.strength > 0.0);
if lantern_exists != enable {
if !enable {
server
.state_mut()
.ecs()
.write_storage::<comp::LightEmitter>()
.remove(entity);
} else if ecs // Only enable lantern if entity is alive
.read_storage::<comp::Health>()
.get(entity)
.map_or(true, |h| !h.is_dead)
{
let inventory_storage = ecs.read_storage::<Inventory>();
let lantern_info = inventory_storage
light_emitters.remove(entity);
}
// Only enable lantern if entity is alive
else if healths.get(entity).map_or(true, |h| !h.is_dead) {
let lantern_info = inventories
.get(entity)
.and_then(|inventory| inventory.equipped(EquipSlot::Lantern))
.and_then(|item| {
@ -69,192 +79,74 @@ pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
}
});
if let Some((col, strength, flicker)) = lantern_info {
let _ =
ecs.write_storage::<comp::LightEmitter>()
.insert(entity, comp::LightEmitter {
let _ = light_emitters.insert(entity, comp::LightEmitter {
col,
strength,
flicker,
animated: true,
});
}
}
}
}
}
}
pub fn handle_npc_interaction(
server: &mut Server,
interactor: EcsEntity,
npc_entity: EcsEntity,
subject: Subject,
impl ServerEvent for NpcInteractEvent {
type SystemData<'a> = (
WriteStorage<'a, comp::Agent>,
ReadStorage<'a, comp::Pos>,
ReadStorage<'a, Uid>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut agents, positions, uids): Self::SystemData<'_>,
) {
let state = server.state_mut();
for NpcInteractEvent(interactor, npc_entity, subject) in events {
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)
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 within_range && let Some(agent) = agents
.get_mut(npc_entity) && agent.target.is_none()
{
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
if let Some(interactor_uid) = uids.get(interactor) {
agent
.inbox
.push_back(AgentEvent::Talk(interactor_uid, subject));
.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(),
impl ServerEvent for SetPetStayEvent {
type SystemData<'a> = (
WriteStorage<'a, comp::Agent>,
WriteStorage<'a, comp::CharacterActivity>,
ReadStorage<'a, comp::Pos>,
ReadStorage<'a, comp::Alignment>,
ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Uid>,
);
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) {
let state = server.state_mut();
state.ecs().write_storage::<Is<Rider>>().remove(rider);
state.ecs().write_storage::<Is<VolumeRider>>().remove(rider);
}
pub fn handle_set_pet_stay(
server: &mut Server,
command_giver: EcsEntity,
pet: EcsEntity,
stay: bool,
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut agents, mut character_activities, positions, alignments, is_mounts, uids): Self::SystemData<'_>,
) {
let state = server.state_mut();
let positions = state.ecs().read_storage::<Pos>();
let is_owner = state
.ecs()
.uid_from_entity(command_giver)
.map_or(false, |owner_uid| {
for SetPetStayEvent(command_giver, pet, stay) in events {
let is_owner = uids.get(command_giver).map_or(false, |owner_uid| {
matches!(
state
.ecs()
.read_storage::<comp::Alignment>()
.get(pet),
Some(comp::Alignment::Owned(pet_owner)) if *pet_owner == owner_uid,
alignments.get(pet),
Some(comp::Alignment::Owned(pet_owner)) if *pet_owner == *owner_uid,
)
});
@ -262,25 +154,16 @@ pub fn handle_set_pet_stay(
let stay = stay && current_pet_position.is_some();
if is_owner
&& within_mounting_range(positions.get(command_giver), positions.get(pet))
&& state.ecs().read_storage::<Is<Mount>>().get(pet).is_none()
&& is_mounts.get(pet).is_none()
{
state
.ecs()
.write_storage::<comp::CharacterActivity>()
character_activities
.get_mut(pet)
.map(|mut activity| activity.is_pet_staying = stay);
state
.ecs()
.write_storage::<comp::Agent>()
agents
.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 {
match (player_position, mount_position) {
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2),
_ => false,
}
}
@ -303,30 +186,50 @@ lazy_static! {
);
}
pub fn handle_mine_block(
server: &mut Server,
entity: EcsEntity,
pos: Vec3<i32>,
tool: Option<ToolKind>,
impl ServerEvent for MineBlockEvent {
type SystemData<'a> = (
WriteExpect<'a, BlockChange>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, MaterialStatManifest>,
ReadExpect<'a, AbilityMap>,
ReadExpect<'a, EventBus<CreateItemDropEvent>>,
ReadExpect<'a, EventBus<Outcome>>,
WriteStorage<'a, comp::SkillSet>,
ReadStorage<'a, Uid>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(
mut block_change,
terrain,
msm,
ability_map,
create_item_drop_events,
outcomes,
mut skill_sets,
uids,
): Self::SystemData<'_>,
) {
let state = server.state_mut();
if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied();
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
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))
{
// 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);
let mut items: Vec<_> =
flatten_counted_items(&items, &ability_map, &msm).collect();
let maybe_uid = uids.get(ev.entity).copied();
if let Some(mut skillset) = state
.ecs()
.write_storage::<comp::SkillSet>()
.get_mut(entity)
{
if let Some(mut skillset) = skill_sets.get_mut(ev.entity) {
if let (Some(tool), Some(uid), exp_reward @ 1..) = (
tool,
ev.tool,
maybe_uid,
items
.iter()
@ -337,26 +240,23 @@ pub fn handle_mine_block(
})
.sum(),
) {
let skill_group = SkillGroupKind::Weapon(tool);
let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>();
let skill_group = comp::SkillGroupKind::Weapon(tool);
if let Some(level_outcome) =
skillset.add_experience(skill_group, exp_reward)
{
outcome_bus.emit_now(Outcome::SkillPointGain {
outcome_emitter.emit(Outcome::SkillPointGain {
uid,
skill_tree: skill_group,
total_points: level_outcome,
});
}
outcome_bus.emit_now(Outcome::ExpChange {
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};
use rand::Rng;
let mut rng = rand::thread_rng();
let need_double_ore = |rng: &mut rand::rngs::ThreadRng| {
let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain);
@ -378,7 +278,8 @@ pub fn handle_mine_block(
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))
|| (id.contains("mineral.gem.")
&& need_double_gem(&mut rng))
});
if double_gain {
@ -390,36 +291,44 @@ pub fn handle_mine_block(
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()),
.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,
);
});
}
}
state.set_block(pos, block.into_vacant());
state
.ecs()
.read_resource::<EventBus<Outcome>>()
.emit_now(Outcome::BreakBlock {
pos,
block_change.set(ev.pos, block.into_vacant());
outcome_emitter.emit(Outcome::BreakBlock {
pos: ev.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>();
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 (agents, positions).join() {
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);
@ -446,66 +355,75 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) {
}),
_ => None,
} {
ecs.read_resource::<EventBus<Outcome>>().emit_now(outcome);
outcome_emitter.emit(outcome);
}
}
}
}
pub fn handle_create_sprite(
server: &mut Server,
pos: Vec3<i32>,
sprite: SpriteKind,
del_timeout: Option<(f32, f32)>,
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<'_>,
) {
let state = server.state_mut();
if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied();
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(sprite);
state.set_block(pos, new_block);
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)) = del_timeout {
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 = state.ecs().read_resource::<Time>().0;
let current_time: f64 = 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)
scheduled_block_change.set(ev.pos, old_block, replace_time);
scheduled_block_change.outcome_set(ev.pos, new_block, replace_time);
}
}
}
}
}
}
}
pub fn handle_tame_pet(server: &mut Server, pet_entity: EcsEntity, owner_entity: EcsEntity) {
// TODO: Raise outcome to send to clients to play sound/render an indicator
// showing taming success?
tame_pet(server.state.ecs(), pet_entity, owner_entity);
}
impl ServerEvent for ToggleSpriteLightEvent {
type SystemData<'a> = (
WriteExpect<'a, BlockChange>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, comp::Pos>,
);
pub fn handle_toggle_sprite_light(
server: &mut Server,
entity: EcsEntity,
pos: Vec3<i32>,
enable: bool,
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(mut block_change, terrain, positions): Self::SystemData<'_>,
) {
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);
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
// showing taming success?
tame_pet(server.state.ecs(), ev.pet_entity, ev.owner_entity);
}

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 crate::{client::Client, Server};
use super::{
event_dispatch,
group_manip::{self, update_map_markers},
ServerEvent,
};
use crate::{client::Client, Settings};
use common::{
comp::{
self,
agent::{Agent, AgentEvent},
group::GroupManager,
invite::{Invite, InviteKind, InviteResponse, PendingInvites},
ChatType, Pos,
ChatType, Group, Pos,
},
consts::MAX_TRADE_RANGE,
event::{InitiateInviteEvent, InviteResponseEvent},
trade::{TradeResult, Trades},
uid::Uid,
uid::{IdMaps, Uid},
};
use common_net::{
msg::{InviteAnswer, ServerGeneral},
sync::WorldSyncExt,
use common_net::msg::{InviteAnswer, ServerGeneral};
use specs::{
shred, DispatcherBuilder, Entities, Entity, ReadExpect, ReadStorage, SystemData, WriteExpect,
WriteStorage,
};
use common_state::State;
use specs::{world::WorldExt, Entity};
use std::time::{Duration, Instant};
use tracing::{error, warn};
@ -26,11 +30,45 @@ const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
/// Reduced duration shown to the client to help alleviate latency issues
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kind: InviteKind) {
let max_group_size = server.settings().max_player_group_size;
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let invitee = match state.ecs().entity_from_uid(invitee_uid) {
pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
event_dispatch::<InitiateInviteEvent>(builder);
event_dispatch::<InviteResponseEvent>(builder);
}
impl ServerEvent for InitiateInviteEvent {
type SystemData<'a> = (
WriteExpect<'a, Trades>,
ReadExpect<'a, Settings>,
ReadExpect<'a, IdMaps>,
ReadExpect<'a, GroupManager>,
WriteStorage<'a, PendingInvites>,
WriteStorage<'a, Agent>,
WriteStorage<'a, Invite>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Client>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Group>,
);
fn handle(
events: impl ExactSizeIterator<Item = Self>,
(
mut trades,
settings,
id_maps,
group_manager,
mut pending_invites,
mut agents,
mut invites,
uids,
clients,
positions,
groups,
): Self::SystemData<'_>,
) {
for InitiateInviteEvent(inviter, invitee_uid, kind) in events {
let max_group_size = settings.max_player_group_size;
let invitee = match id_maps.uid_entity(invitee_uid) {
Some(t) => t,
None => {
// Inform of failure
@ -44,8 +82,6 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
},
};
let uids = state.ecs().read_storage::<Uid>();
// Check if entity is trying to invite themselves
if uids
.get(inviter)
@ -55,13 +91,8 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
return;
}
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let mut agents = state.ecs().write_storage::<Agent>();
let mut invites = state.ecs().write_storage::<Invite>();
if let InviteKind::Trade = kind {
if matches!(kind, InviteKind::Trade) {
// Check whether the inviter is in range of the invitee
let positions = state.ecs().read_storage::<Pos>();
if !within_trading_range(positions.get(inviter), positions.get(invitee)) {
return;
}
@ -69,8 +100,9 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
if let InviteKind::Group = kind {
if !group_manip::can_invite(
state,
&clients,
&groups,
&group_manager,
&mut pending_invites,
max_group_size,
inviter,
@ -80,21 +112,21 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
}
} 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))
.and_then(|u| id_maps.uid_entity(u))
.map(|e| {
if let Some(client) = clients.get(e) {
client
.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
client.send_fallible(ServerGeneral::FinishedTrade(
TradeResult::Declined,
));
}
if let Some(agent) = agents.get_mut(e) {
agent
.inbox
.push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
agent.inbox.push_back(AgentEvent::FinishedTrade(
TradeResult::Declined,
));
}
});
}
@ -146,7 +178,9 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
};
// If client comp
if let (Some(client), Some(inviter)) = (clients.get(invitee), uids.get(inviter).copied()) {
if let (Some(client), Some(inviter)) =
(clients.get(invitee), uids.get(inviter).copied())
{
if send_invite() {
client.send_fallible(ServerGeneral::Invite {
inviter,
@ -175,57 +209,87 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
}
}
}
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) {
let index = server.index.clone();
let state = server.state_mut();
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>();
#[derive(SystemData)]
pub struct InviteResponseData<'a> {
entities: Entities<'a>,
group_manager: WriteExpect<'a, GroupManager>,
trades: WriteExpect<'a, Trades>,
index: ReadExpect<'a, world::IndexOwned>,
id_maps: ReadExpect<'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_accept(data: &mut InviteResponseData, entity: Entity) {
if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
handle_invite_answer(data, inviter, entity, InviteAnswer::Accepted, kind);
match kind {
InviteKind::Group => {
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
data.group_manager.add_group_member(
inviter,
entity,
&state.ecs().entities(),
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
&data.entities,
&mut data.groups,
&data.alignments,
&data.uids,
|entity, group_change| {
clients
data.clients
.get(entity)
.and_then(|c| {
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, 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));
});
},
);
},
InviteKind::Trade => {
if let (Some(inviter_uid), Some(invitee_uid)) =
(uids.get(inviter).copied(), uids.get(entity).copied())
{
let mut trades = state.ecs().write_resource::<Trades>();
if let (Some(inviter_uid), Some(invitee_uid)) = (
data.uids.get(inviter).copied(),
data.uids.get(entity).copied(),
) {
// check if the person that invited me has started a new trade since the
// invitation was sent
if trades.entity_trades.get(&inviter_uid).copied().is_some() {
for client in clients.get(entity).into_iter().chain(clients.get(inviter)) {
if data
.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(
ChatType::Meta,
"Trade failed, inviter initiated new trade since sending trade \
@ -234,39 +298,40 @@ pub fn handle_invite_accept(server: &mut Server, entity: Entity) {
}
return;
}
let id = trades.begin_trade(inviter_uid, invitee_uid);
let trade = trades.trades[&id].clone();
if let Some(agent) = agents.get_mut(inviter) {
let id = data.trades.begin_trade(inviter_uid, invitee_uid);
let trade = data.trades.trades[&id].clone();
if let Some(agent) = data.agents.get_mut(inviter) {
agent
.inbox
.push_back(AgentEvent::TradeAccepted(invitee_uid));
}
#[cfg(feature = "worldgen")]
let pricing = agents
let pricing = data
.agents
.get(inviter)
.and_then(|a| {
a.behavior
.trade_site()
.and_then(|id| index.get_site_prices(id))
.and_then(|id| data.index.get_site_prices(id))
})
.or_else(|| {
agents.get(entity).and_then(|a| {
data.agents.get(entity).and_then(|a| {
a.behavior
.trade_site()
.and_then(|id| index.get_site_prices(id))
.and_then(|id| data.index.get_site_prices(id))
})
});
#[cfg(not(feature = "worldgen"))]
let pricing = None;
clients.get(inviter).map(|c| {
data.clients.get(inviter).map(|c| {
c.send(ServerGeneral::UpdatePendingTrade(
id,
trade.clone(),
pricing.clone(),
))
});
clients
data.clients
.get(entity)
.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)> {
let mut invites = state.ecs().write_storage::<Invite>();
invites.remove(entity).and_then(|invite| {
fn get_inviter_and_kind(
entity: Entity,
data: &mut InviteResponseData,
) -> Option<(Entity, InviteKind)> {
data.invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let pending = &mut pending_invites.get_mut(inviter)?.0;
let pending = &mut data.pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index);
// If no pending invites remain remove the component
if pending.is_empty() {
pending_invites.remove(inviter);
data.pending_invites.remove(inviter);
}
Some((inviter, kind))
@ -294,28 +360,25 @@ fn get_inviter_and_kind(entity: Entity, state: &mut State) -> Option<(Entity, In
}
fn handle_invite_answer(
state: &mut State,
data: &mut InviteResponseData,
inviter: Entity,
entity: Entity,
invite_answer: InviteAnswer,
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) {
// invitee must close current trade if one exists before accepting new one
if let Some(invitee_uid) = uids.get(entity).copied() {
let mut trades = state.ecs().write_resource::<Trades>();
if let Some(active_trade) = trades.entity_trades.get(&invitee_uid).copied() {
trades
if let Some(invitee_uid) = data.uids.get(entity).copied() {
if let Some(active_trade) = data.trades.entity_trades.get(&invitee_uid).copied() {
data.trades
.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| {
if let Some(client) = clients.get(e) {
if let Some(client) = data.clients.get(e) {
client
.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
.inbox
.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 {
target,
answer: invite_answer,
@ -333,11 +398,10 @@ fn handle_invite_answer(
}
}
pub fn handle_invite_decline(server: &mut Server, entity: Entity) {
let state = server.state_mut();
if let Some((inviter, kind)) = get_inviter_and_kind(entity, state) {
pub fn handle_invite_decline(data: &mut InviteResponseData, entity: Entity) {
if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
// 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,31 @@
use crate::{
events::{
entity_creation::handle_create_teleporter,
entity_manipulation::{handle_start_teleporting, handle_teleport_to_position},
interaction::{handle_mount_volume, handle_tame_pet},
},
persistence::PersistedComponents,
state_ext::StateExt,
Server,
};
use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
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 std::{marker::PhantomData, sync::Arc};
use crate::{state_ext::StateExt, Server};
use common::event::{
ChatEvent, ClientDisconnectEvent, ClientDisconnectWithoutPersistenceEvent, CommandEvent,
EventBus, ExitIngameEvent,
};
use common_base::span;
use common_ecs::{dispatch, System};
use specs::{DispatcherBuilder, Entity as EcsEntity, ReadExpect, WorldExt};
use crate::events::player::handle_character_delete;
pub use group_manip::update_map_markers;
pub(crate) use trade::cancel_trades_for;
use self::{
entity_creation::{
handle_create_npc, handle_create_ship, handle_create_teleporter, handle_create_waypoint,
handle_initialize_character, handle_initialize_spectator, handle_loaded_character_data,
handle_shockwave, handle_shoot,
},
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_manipulation;
mod group_manip;
@ -45,9 +33,56 @@ mod information;
mod interaction;
mod inventory_manip;
mod invite;
mod mounting;
mod player;
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> System<'a> for EventHandler<T> {
type SystemData = (
ReadExpect<'a, crate::metrics::ServerEventMetrics>,
ReadExpect<'a, EventBus<T>>,
T::SystemData<'a>,
);
const NAME: &'static str = T::NAME;
const ORIGIN: common_ecs::Origin = common_ecs::Origin::Server;
// TODO: Maybe do another phase here?
const PHASE: common_ecs::Phase = common_ecs::Phase::Apply;
fn run(_job: &mut common_ecs::Job<Self>, (metrics, ev, data): Self::SystemData) {
let events = ev.recv_all();
metrics
.event_count
.with_label_values(&[Self::NAME])
.inc_by(events.len() as u64);
T::handle(events, data)
}
}
fn event_dispatch<T: ServerEvent>(builder: &mut DispatcherBuilder) {
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 {
ClientConnected {
entity: EcsEntity,
@ -62,280 +97,91 @@ pub enum Event {
}
impl Server {
pub fn handle_events(&mut self) -> Vec<Event> {
span!(_guard, "handle_events", "Server::handle_events");
let mut frontend_events = Vec::new();
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),
}
}
{
fn handle_serial_events<T: Send + 'static, F: FnMut(&mut Self, T)>(&mut self, mut f: F) {
if let Some(bus) = self.state.ecs_mut().get_mut::<EventBus<T>>() {
let events = bus.recv_all_mut();
let server_event_metrics = self
.state
.ecs()
.read_resource::<crate::metrics::ServerEventMetrics>();
event_counts
.into_iter()
.zip(ServerEventDiscriminants::VARIANTS)
.for_each(|(count, event_name)| {
server_event_metrics
.event_count
.with_label_values(&[event_name])
.inc_by(count.into());
})
.with_label_values(&[std::any::type_name::<T>()])
.inc_by(events.len() as u64);
drop(server_event_metrics);
for ev in events {
f(self, ev)
}
}
}
for (entity, name, args) in commands {
self.process_command(entity, name, args);
fn handle_all_serial_events(&mut self, frontend_events: &mut Vec<Event>) {
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_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);
}
for msg in chat_messages {
self.state.send_chat(msg);
}
pub fn handle_events(&mut self) -> Vec<Event> {
let mut frontend_events = Vec::new();
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(Arc::clone(self.state.thread_pool()));
register_event_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel.
let mut dispatcher = dispatch_builder.build();
drop(guard);
span!(guard, "run event systems");
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
}

View File

@ -0,0 +1,137 @@
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,
};
use common::{
character::CharacterId,
comp,
comp::{group, pet::is_tameable, Presence, PresenceKind},
event::{DeleteCharacterEvent, PossessEvent},
resources::Time,
uid::{IdMaps, Uid},
};
@ -16,12 +16,7 @@ use common_state::State;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use tracing::{debug, error, trace, warn, Instrument};
pub fn handle_character_delete(
server: &mut Server,
entity: EcsEntity,
requesting_player_uuid: String,
character_id: CharacterId,
) {
pub fn handle_character_delete(server: &mut Server, ev: DeleteCharacterEvent) {
// Can't process a character delete for a player that has an in-game presence,
// so kick them out before processing the delete.
// NOTE: This relies on StateExt::handle_initialize_character adding the
@ -29,19 +24,19 @@ pub fn handle_character_delete(
// is in-game.
let has_presence = {
let presences = server.state.ecs().read_storage::<Presence>();
presences.get(entity).is_some()
presences.get(ev.entity).is_some()
};
if has_presence {
warn!(
?requesting_player_uuid,
?character_id,
?ev.requesting_player_uuid,
?ev.character_id,
"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>();
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) {
@ -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
/// comment it out, but it needs to be fixed for a variety of reasons. Get rid
/// 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 common::{
comp::{inventory::slot::EquipSlot, item, slot::Slot, Inventory},

View File

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

View File

@ -5,7 +5,14 @@
clippy::needless_pass_by_ref_mut //until we find a better way for specs
)]
#![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;
mod character_creator;
@ -72,7 +79,10 @@ use common::{
character::{CharacterId, CharacterItem},
cmd::ServerChatCommand,
comp,
event::{EventBus, ServerEvent},
event::{
register_event_busses, ClientDisconnectEvent, ClientDisconnectWithoutPersistenceEvent,
EventBus, ExitIngameEvent, UpdateCharacterDataEvent,
},
link::Is,
mounting::{Volume, VolumeRider},
region::RegionMap,
@ -324,7 +334,8 @@ impl Server {
state.ecs_mut().insert(DataDir {
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()
@ -989,7 +1000,7 @@ impl Server {
),
},
CharacterScreenResponseKind::CharacterData(result) => {
let message = match *result {
match *result {
Ok((character_data, skill_set_persistence_load_error)) => {
let PersistedComponents {
body,
@ -1013,11 +1024,11 @@ impl Server {
);
// TODO: Does this need to be a server event? E.g. we could
// just handle it here.
ServerEvent::UpdateCharacterData {
self.state.emit_event_now(UpdateCharacterDataEvent {
entity: response.target_entity,
components: character_data,
metadata: skill_set_persistence_load_error,
}
})
},
Err(error) => {
// We failed to load data for the character from the DB. Notify
@ -1031,16 +1042,11 @@ impl Server {
);
// Clean up the entity data on the server
ServerEvent::ExitIngame {
self.state.emit_event_now(ExitIngameEvent {
entity: response.target_entity,
}
})
},
};
self.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(message);
}
},
}
},
@ -1194,15 +1200,15 @@ impl Server {
);
for (_, entity) in (&clients, &entities).join() {
info!("Emitting client disconnect event for entity: {:?}", entity);
let event = if with_persistence {
ServerEvent::ClientDisconnect(entity, comp::DisconnectReason::Kicked)
if with_persistence {
self.state.emit_event_now(ClientDisconnectEvent(
entity,
comp::DisconnectReason::Kicked,
))
} else {
ServerEvent::ClientDisconnectWithoutPersistence(entity)
};
self.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(event);
.emit_event_now(ClientDisconnectWithoutPersistenceEvent(entity))
};
}
self.disconnect_all_clients_requested = false;

View File

@ -5,7 +5,7 @@ use crate::sys::terrain::NpcData;
use common::{
calendar::Calendar,
comp::{self, Agent, Body, Presence, PresenceKind},
event::{EventBus, NpcBuilder, ServerEvent},
event::{CreateNpcEvent, CreateShipEvent, DeleteEvent, EventBus, NpcBuilder},
generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Actor, NpcId, RtSimEntity},
@ -236,7 +236,9 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>,
Read<'a, Time>,
Read<'a, TimeOfDay>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, EventBus<CreateShipEvent>>,
Read<'a, EventBus<CreateNpcEvent>>,
Read<'a, EventBus<DeleteEvent>>,
WriteExpect<'a, RtSim>,
ReadExpect<'a, Arc<world::World>>,
ReadExpect<'a, world::IndexOwned>,
@ -259,7 +261,9 @@ impl<'a> System<'a> for Sys {
dt,
time,
time_of_day,
server_event_bus,
create_ship_events,
create_npc_events,
delete_events,
mut rtsim,
world,
index,
@ -271,7 +275,9 @@ impl<'a> System<'a> for Sys {
calendar,
): 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 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 {
Body::Ship(body) => {
emitter.emit(ServerEvent::CreateShip {
create_ship_emitter.emit(CreateShipEvent {
pos: comp::Pos(npc.wpos),
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
ship: body,
@ -333,7 +339,7 @@ impl<'a> System<'a> for Sys {
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 {
pos,
stats,
@ -346,7 +352,7 @@ impl<'a> System<'a> for Sys {
alignment,
scale,
loot,
} => ServerEvent::CreateNpc {
} => CreateNpcEvent {
pos,
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
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
for (entity, pos, rtsim_entity, agent) in (
&entities,
@ -489,7 +494,7 @@ impl<'a> System<'a> for Sys {
}
},
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 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 tracing::{error, trace, warn};
use vek::*;
@ -1253,28 +1256,47 @@ impl StateExt for State {
dismount_volume: bool,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> Result<T, Content> {
if dismount_volume {
self.ecs().write_storage::<Is<VolumeRider>>().remove(entity);
let ecs = self.ecs_mut();
position_mut(
entity,
dismount_volume,
f,
&ecs.read_resource(),
&mut ecs.write_storage(),
ecs.write_storage(),
ecs.write_storage(),
ecs.read_storage(),
ecs.read_storage(),
ecs.read_storage(),
)
}
}
let entity = self
.read_storage::<Is<Rider>>()
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| {
self.ecs()
.read_resource::<IdMaps>()
.uid_entity(is_rider.mount)
})
.and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
.map(Ok)
.or_else(|| {
self.read_storage::<Is<VolumeRider>>()
.get(entity)
.and_then(|volume_rider| {
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(self.ecs().read_resource::<IdMaps>().uid_entity(uid)?)
},
common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
})
})
})
@ -1282,9 +1304,7 @@ impl StateExt for State {
let mut maybe_pos = None;
let res = self
.ecs()
.write_storage::<comp::Pos>()
let res = positions
.get_mut(entity)
.map(|pos| {
let res = f(pos);
@ -1297,19 +1317,16 @@ impl StateExt for State {
));
if let Some(pos) = maybe_pos {
if self
.ecs()
.read_storage::<Presence>()
if presences
.get(entity)
.map(|presence| presence.kind == PresenceKind::Spectator)
.unwrap_or(false)
{
self.read_storage::<Client>().get(entity).map(|client| {
clients.get(entity).map(|client| {
client.send_fallible(ServerGeneral::SpectatePosition(pos));
});
} else {
self.ecs()
.write_storage::<comp::ForceUpdate>()
force_updates
.get_mut(entity)
.map(|force_update| force_update.update());
}
@ -1317,7 +1334,6 @@ impl StateExt for State {
res
}
}
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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ use crate::{
};
use common::{
comp::{self, Admin, Player, Stats},
event::{EventBus, ServerEvent},
event::{ClientDisconnectEvent, EventBus, MakeAdminEvent},
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
resources::TimeOfDay,
shared_server_config::ServerConstants,
@ -42,7 +42,8 @@ pub struct ReadData<'a> {
entities: Entities<'a>,
stats: ReadStorage<'a, Stats>,
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>,
player_metrics: ReadExpect<'a, PlayerMetrics>,
settings: ReadExpect<'a, Settings>,
@ -62,7 +63,6 @@ pub struct ReadData<'a> {
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, EventBus<ServerEvent>>,
ReadData<'a>,
WriteStorage<'a, Client>,
WriteStorage<'a, Player>,
@ -75,8 +75,9 @@ impl<'a> System<'a> for Sys {
fn run(
_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
// have to do a linear scan over all entities on each login to see if
// 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.
.par_bridge()
.for_each_init(
|| read_data.server_event_bus.emitter(),
|server_emitter, (entity, uid, client, _, pending)| {
|| read_data.client_disconnect_events.emitter(),
|client_disconnect_emitter, (entity, uid, client, _, pending)| {
prof_span!("msg::register login");
if let Err(e) = || -> Result<(), crate::error::Error> {
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
// the lock.
trace!(?e, "pending login returned error");
server_emitter.emit(ServerEvent::ClientDisconnect(
client_disconnect_emitter.emit(ClientDisconnectEvent(
entity,
common::comp::DisconnectReason::Kicked,
));
@ -302,7 +303,7 @@ impl<'a> System<'a> for Sys {
);
}
// Remove old client
server_emitter.emit(ServerEvent::ClientDisconnect(
client_disconnect_emitter.emit(ClientDisconnectEvent(
old_entity,
common::comp::DisconnectReason::NewerLogin,
));
@ -408,7 +409,7 @@ impl<'a> System<'a> for Sys {
if let Some(admin) = admin {
// We need to defer writing to the Admin storage since it's borrowed immutably
// by this system via TrackedStorages.
event_bus.emit_now(ServerEvent::MakeAdmin {
make_admin_emitter.emit(MakeAdminEvent {
entity,
admin: Admin(admin.role.into()),
uuid,

View File

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

View File

@ -2,7 +2,8 @@ use common::{
comp::{object, Body, Object, PhysicsState, Pos, Teleporting, Vel},
consts::TELEPORTER_RADIUS,
effect::Effect,
event::{EventBus, ServerEvent},
event::{ChangeBodyEvent, DeleteEvent, EmitExt, EventBus, ExplosionEvent, ShootEvent},
event_emitters,
outcome::Outcome,
resources::{DeltaTime, Time},
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 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
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Events<'a>,
Read<'a, DeltaTime>,
Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, EventBus<Outcome>>,
Read<'a, CachedSpatialGrid>,
ReadStorage<'a, Pos>,
@ -38,9 +48,9 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>,
(
entities,
events,
_dt,
time,
server_bus,
outcome_bus,
spatial_grid,
positions,
@ -51,7 +61,7 @@ impl<'a> System<'a> for Sys {
teleporting,
): Self::SystemData,
) {
let mut server_emitter = server_bus.emitter();
let mut emitters = events.get_emitters();
// Objects
for (entity, pos, vel, physics, object, body) in (
@ -67,8 +77,8 @@ impl<'a> System<'a> for Sys {
match object {
Object::Bomb { owner } => {
if physics.on_surface().is_some() {
server_emitter.emit(ServerEvent::Delete(entity));
server_emitter.emit(ServerEvent::Explosion {
emitters.emit(DeleteEvent(entity));
emitters.emit(ExplosionEvent {
pos: pos.0,
explosion: Explosion {
effects: vec![
@ -132,7 +142,7 @@ impl<'a> System<'a> for Sys {
phi.sin(),
))
.expect("nonzero vector should normalize");
server_emitter.emit(ServerEvent::Shoot {
emitters.emit(ShootEvent {
entity,
pos: *pos,
dir,
@ -160,8 +170,8 @@ impl<'a> System<'a> for Sys {
});
}
}
server_emitter.emit(ServerEvent::Delete(entity));
server_emitter.emit(ServerEvent::Explosion {
emitters.emit(DeleteEvent(entity));
emitters.emit(ExplosionEvent {
pos: pos.0,
explosion: Explosion {
effects: vec![
@ -186,7 +196,7 @@ impl<'a> System<'a> for Sys {
timeout,
} => {
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 { .. } => {
@ -204,7 +214,7 @@ impl<'a> System<'a> for Sys {
});
if (*body == Body::Object(object::Body::PortalActive)) != is_active {
server_bus.emit_now(ServerEvent::ChangeBody {
emitters.emit(ChangeBodyEvent {
entity,
new_body: Body::Object(if is_active {
outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 });

View File

@ -1,7 +1,7 @@
use common::{
comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
consts::TELEPORTER_RADIUS,
event::{EventBus, ServerEvent},
event::{EventBus, TeleportToPositionEvent},
outcome::Outcome,
resources::Time,
uid::Uid,
@ -33,7 +33,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, CharacterState>,
Read<'a, CachedSpatialGrid>,
Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, EventBus<TeleportToPositionEvent>>,
Read<'a, EventBus<Outcome>>,
);
@ -54,10 +54,12 @@ impl<'a> System<'a> for Sys {
character_states,
spatial_grid,
time,
server_bus,
teleport_to_position_events,
outcome_bus,
): 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>| {
spatial_grid
.0
@ -126,11 +128,11 @@ impl<'a> System<'a> for Sys {
for entity in nearby {
cancel_teleporting.push(entity);
server_bus.emit_now(ServerEvent::TeleportToPosition {
teleport_to_position_emitter.emit(TeleportToPositionEvent {
entity,
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,
BehaviorCapability, ForceUpdate, Pos, Presence, Waypoint,
},
event::{EventBus, NpcBuilder, ServerEvent},
event::{
CreateNpcEvent, CreateTeleporterEvent, CreateWaypointEvent, EmitExt, EventBus, NpcBuilder,
},
event_emitters,
generation::{EntityInfo, SpecialEntity},
lottery::LootSpec,
resources::{Time, TimeOfDay},
@ -51,6 +54,14 @@ type RtSimData<'a> = WriteExpect<'a, rtsim::RtSim>;
#[cfg(not(feature = "worldgen"))]
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
/// unneeded chunks.
/// 1. Inserts newly generated chunks into the TerrainGrid
@ -62,7 +73,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)]
type SystemData = (
Read<'a, EventBus<ServerEvent>>,
Events<'a>,
Read<'a, Tick>,
Read<'a, Settings>,
Read<'a, TimeOfDay>,
@ -94,7 +105,7 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(
server_event_bus,
events,
tick,
server_settings,
time_of_day,
@ -119,7 +130,7 @@ impl<'a> System<'a> for Sys {
time,
): Self::SystemData,
) {
let mut server_emitter = server_event_bus.emitter();
let mut emitters = events.get_emitters();
// Generate requested chunks
//
@ -196,7 +207,7 @@ impl<'a> System<'a> for Sys {
let data = NpcData::from_entity_info(entity);
match data {
NpcData::Waypoint(pos) => {
server_emitter.emit(ServerEvent::CreateWaypoint(pos));
emitters.emit(CreateWaypointEvent(pos));
},
NpcData::Data {
pos,
@ -211,7 +222,7 @@ impl<'a> System<'a> for Sys {
scale,
loot,
} => {
server_emitter.emit(ServerEvent::CreateNpc {
emitters.emit(CreateNpcEvent {
pos,
ori: comp::Ori::from(Dir::random_2d(&mut rng)),
npc: NpcBuilder::new(stats, body, alignment)
@ -227,7 +238,7 @@ impl<'a> System<'a> for Sys {
});
},
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 common::{
comp::{LightEmitter, PhysicsState, Pos},
event::{EventBus, ServerEvent},
event, event_emitters,
resources::EntitiesDiedLastTick,
};
use common_ecs::{Job, Origin, Phase, System};
@ -20,13 +20,19 @@ pub struct ReadData<'a> {
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)
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadData<'a>,
Read<'a, EventBus<ServerEvent>>,
Events<'a>,
WriteStorage<'a, WiringElement>,
WriteStorage<'a, LightEmitter>, // maybe
Write<'a, BlockChange>,
@ -38,9 +44,9 @@ impl<'a> System<'a> for Sys {
fn run(
_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
// 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,
physics_state,
&read_data.entities_died_last_tick.0,
&mut server_emitter,
&mut emitters,
pos,
&mut block_change,
light_emitter.as_deref_mut(),

View File

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