From d7ba4ecef7b39c37eb499a8fe6f354d7ae4ac3c8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 5 Apr 2023 23:24:16 +0100 Subject: [PATCH] Allow OnDeath event to handle all actors --- rtsim/src/event.rs | 10 +- rtsim/src/rule/simulate_npcs.rs | 163 ++++++++++++----------- rtsim/src/rule/sync_npcs.rs | 24 ++-- server/src/cmd.rs | 6 +- server/src/events/entity_manipulation.rs | 37 ++--- server/src/rtsim/mod.rs | 11 +- server/src/state_ext.rs | 23 ++++ 7 files changed, 158 insertions(+), 116 deletions(-) diff --git a/rtsim/src/event.rs b/rtsim/src/event.rs index 65d1ea213e..47d7802698 100644 --- a/rtsim/src/event.rs +++ b/rtsim/src/event.rs @@ -1,5 +1,8 @@ -use crate::{data::NpcId, RtState, Rule}; -use common::resources::{Time, TimeOfDay}; +use crate::{RtState, Rule}; +use common::{ + resources::{Time, TimeOfDay}, + rtsim::Actor, +}; use world::{IndexRef, World}; pub trait Event: Clone + 'static {} @@ -27,6 +30,7 @@ impl Event for OnTick {} #[derive(Clone)] pub struct OnDeath { - pub npc_id: NpcId, + pub actor: Actor, + pub killer: Option, } impl Event for OnDeath {} diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index 7bfe9bfab1..cd9d92fabe 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -45,88 +45,89 @@ fn on_setup(ctx: EventCtx) { fn on_death(ctx: EventCtx) { let data = &mut *ctx.state.data_mut(); - let npc_id = ctx.event.npc_id; - let Some(npc) = data.npcs.get(npc_id) else { - return; - }; - let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>()); - // Respawn dead NPCs - match npc.body { - Body::Humanoid(_) => { - if let Some((site_id, site)) = data - .sites - .iter() - .filter(|(id, site)| { - Some(*id) != npc.home - && site.faction == npc.faction - && site.world_site.map_or(false, |s| { - matches!(ctx.index.sites.get(s).kind, SiteKind::Refactor(_)) - }) - }) - .min_by_key(|(_, site)| site.population.len()) - { - let rand_wpos = |rng: &mut ChaChaRng| { - let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); - wpos2d - .map(|e| e as f32 + 0.5) - .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) - }; - let random_humanoid = |rng: &mut ChaChaRng| { - let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap(); - Body::Humanoid(comp::humanoid::Body::random_with(rng, species)) - }; - data.spawn_npc( - Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) - .with_personality(Personality::random(&mut rng)) - .with_home(site_id) - .with_faction(npc.faction) - .with_profession(npc.profession.clone()), - ); - } else { - warn!("No site found for respawning humaniod"); - } - }, - Body::BirdLarge(_) => { - if let Some((site_id, site)) = data - .sites - .iter() - .filter(|(id, site)| { - Some(*id) != npc.home - && site.world_site.map_or(false, |s| { - matches!(ctx.index.sites.get(s).kind, SiteKind::Dungeon(_)) - }) - }) - .min_by_key(|(_, site)| site.population.len()) - { - let rand_wpos = |rng: &mut ChaChaRng| { - let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); - wpos2d - .map(|e| e as f32 + 0.5) - .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) - }; - let species = [ - comp::body::bird_large::Species::Phoenix, - comp::body::bird_large::Species::Cockatrice, - comp::body::bird_large::Species::Roc, - ] - .choose(&mut rng) - .unwrap(); - data.npcs.create_npc( - Npc::new( - rng.gen(), - rand_wpos(&mut rng), - Body::BirdLarge(comp::body::bird_large::Body::random_with( - &mut rng, species, - )), - ) - .with_home(site_id), - ); - } else { - warn!("No site found for respawning bird"); - } - }, - _ => unimplemented!(), + if let Actor::Npc(npc_id) = ctx.event.actor + && let Some(npc) = data.npcs.get(npc_id) + { + let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>()); + + // Respawn dead NPCs + match npc.body { + Body::Humanoid(_) => { + if let Some((site_id, site)) = data + .sites + .iter() + .filter(|(id, site)| { + Some(*id) != npc.home + && site.faction == npc.faction + && site.world_site.map_or(false, |s| { + matches!(ctx.index.sites.get(s).kind, SiteKind::Refactor(_)) + }) + }) + .min_by_key(|(_, site)| site.population.len()) + { + let rand_wpos = |rng: &mut ChaChaRng| { + let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); + wpos2d + .map(|e| e as f32 + 0.5) + .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) + }; + let random_humanoid = |rng: &mut ChaChaRng| { + let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap(); + Body::Humanoid(comp::humanoid::Body::random_with(rng, species)) + }; + data.spawn_npc( + Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) + .with_personality(Personality::random(&mut rng)) + .with_home(site_id) + .with_faction(npc.faction) + .with_profession(npc.profession.clone()), + ); + } else { + warn!("No site found for respawning humaniod"); + } + }, + Body::BirdLarge(_) => { + if let Some((site_id, site)) = data + .sites + .iter() + .filter(|(id, site)| { + Some(*id) != npc.home + && site.world_site.map_or(false, |s| { + matches!(ctx.index.sites.get(s).kind, SiteKind::Dungeon(_)) + }) + }) + .min_by_key(|(_, site)| site.population.len()) + { + let rand_wpos = |rng: &mut ChaChaRng| { + let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); + wpos2d + .map(|e| e as f32 + 0.5) + .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) + }; + let species = [ + comp::body::bird_large::Species::Phoenix, + comp::body::bird_large::Species::Cockatrice, + comp::body::bird_large::Species::Roc, + ] + .choose(&mut rng) + .unwrap(); + data.npcs.create_npc( + Npc::new( + rng.gen(), + rand_wpos(&mut rng), + Body::BirdLarge(comp::body::bird_large::Body::random_with( + &mut rng, species, + )), + ) + .with_home(site_id), + ); + } else { + warn!("No site found for respawning bird"); + } + }, + _ => unimplemented!(), + } } } diff --git a/rtsim/src/rule/sync_npcs.rs b/rtsim/src/rule/sync_npcs.rs index 61b81939af..3b22c38220 100644 --- a/rtsim/src/rule/sync_npcs.rs +++ b/rtsim/src/rule/sync_npcs.rs @@ -2,7 +2,7 @@ use crate::{ event::{EventCtx, OnDeath, OnSetup, OnTick}, RtState, Rule, RuleError, }; -use common::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize}; +use common::{grid::Grid, rtsim::Actor, terrain::TerrainChunkSize, vol::RectVolSize}; pub struct SyncNpcs; @@ -33,14 +33,20 @@ fn on_setup(ctx: EventCtx) { fn on_death(ctx: EventCtx) { let data = &mut *ctx.state.data_mut(); - // Remove NPC from home population - if let Some(home) = data - .npcs - .get(ctx.event.npc_id) - .and_then(|npc| npc.home) - .and_then(|home| data.sites.get_mut(home)) - { - home.population.remove(&ctx.event.npc_id); + if let Actor::Npc(npc_id) = ctx.event.actor { + // Remove NPC from home population + if let Some(home) = data + .npcs + .get(npc_id) + .and_then(|npc| npc.home) + .and_then(|home| data.sites.get_mut(home)) + { + home.population.remove(&npc_id); + } + + // TODO: Maybe death should be a marker flag instead of outright deleting the + // NPC so that we can still query details about them? + data.npcs.remove(npc_id); } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 1ea826763d..7e2f3dba62 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -42,6 +42,7 @@ use common::{ outcome::Outcome, parse_cmd_args, resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay}, + rtsim::Actor, terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize}, uid::{Uid, UidAllocator}, vol::ReadVol, @@ -2090,10 +2091,11 @@ fn handle_kill_npcs( .copied() { ecs.write_resource::() - .hook_rtsim_entity_delete( + .hook_rtsim_actor_death( &ecs.read_resource::>(), ecs.read_resource::().as_index_ref(), - rtsim_entity, + Actor::Npc(rtsim_entity.0), + None, ); } Some(entity) diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 4530376071..125ce6da6f 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -26,7 +26,6 @@ use common::{ event::{EventBus, ServerEvent}, outcome::{HealthChangeInfo, Outcome}, resources::{Secs, Time}, - rtsim::RtSimEntity, states::utils::StageSection, terrain::{Block, BlockKind, TerrainGrid}, uid::{Uid, UidAllocator}, @@ -519,23 +518,31 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt } if should_delete { - if let Some(rtsim_entity) = state - .ecs() - .read_storage::() - .get(entity) - .copied() - { + if let Some(actor) = state.entity_as_actor(entity) { state .ecs() .write_resource::() - .hook_rtsim_entity_delete( - &state.ecs().read_resource::>(), - state - .ecs() - .read_resource::() - .as_index_ref(), - rtsim_entity, - ); + .hook_rtsim_actor_death( + &state.ecs().read_resource::>(), + state + .ecs() + .read_resource::() + .as_index_ref(), + actor, + last_change + .by + .as_ref() + .and_then( + |(DamageContributor::Solo(entity_uid) + | DamageContributor::Group { entity_uid, .. })| { + state + .ecs() + .read_resource::() + .retrieve_entity_internal((*entity_uid).into()) + }, + ) + .and_then(|killer| state.entity_as_actor(killer)), + ); } if let Err(e) = state.delete_entity_recorded(entity) { diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index e74a5e0eaa..dd10116246 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -5,7 +5,7 @@ pub mod tick; use atomicwrites::{AtomicFile, OverwriteBehavior}; use common::{ grid::Grid, - rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings}, + rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings}, terrain::Block, }; use common_ecs::dispatch; @@ -164,15 +164,14 @@ impl RtSim { } } - pub fn hook_rtsim_entity_delete( + pub fn hook_rtsim_actor_death( &mut self, world: &World, index: IndexRef, - entity: RtSimEntity, + actor: Actor, + killer: Option, ) { - // Should entity deletion be death? They're not exactly the same thing... - self.state.emit(OnDeath { npc_id: entity.0 }, world, index); - self.state.data_mut().npcs.remove(entity.0); + self.state.emit(OnDeath { actor, killer }, world, index); } pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 872dd1a56c..c56a46bf90 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -25,6 +25,7 @@ use common::{ link::{Link, LinkHandle}, mounting::Mounting, resources::{Secs, Time, TimeOfDay}, + rtsim::{Actor, RtSimEntity}, slowjob::SlowJobPool, uid::{Uid, UidAllocator}, LoadoutBuilder, ViewDistances, @@ -139,6 +140,8 @@ pub trait StateExt { &mut self, entity: EcsEntity, ) -> Result<(), specs::error::WrongGeneration>; + /// Get the given entity as an [`Actor`], if it is one. + fn entity_as_actor(&self, entity: EcsEntity) -> Option; } impl StateExt for State { @@ -1103,6 +1106,26 @@ impl StateExt for State { } res } + + fn entity_as_actor(&self, entity: EcsEntity) -> Option { + if let Some(rtsim_entity) = self + .ecs() + .read_storage::() + .get(entity) + .copied() + { + Some(Actor::Npc(rtsim_entity.0)) + } else if let Some(PresenceKind::Character(character)) = self + .ecs() + .read_storage::() + .get(entity) + .map(|p| p.kind) + { + Some(Actor::Character(character)) + } else { + None + } + } } fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {