Allow OnDeath event to handle all actors

This commit is contained in:
Joshua Barretto 2023-04-05 23:24:16 +01:00
parent ce5ef481e1
commit d7ba4ecef7
7 changed files with 158 additions and 116 deletions

View File

@ -1,5 +1,8 @@
use crate::{data::NpcId, RtState, Rule}; use crate::{RtState, Rule};
use common::resources::{Time, TimeOfDay}; use common::{
resources::{Time, TimeOfDay},
rtsim::Actor,
};
use world::{IndexRef, World}; use world::{IndexRef, World};
pub trait Event: Clone + 'static {} pub trait Event: Clone + 'static {}
@ -27,6 +30,7 @@ impl Event for OnTick {}
#[derive(Clone)] #[derive(Clone)]
pub struct OnDeath { pub struct OnDeath {
pub npc_id: NpcId, pub actor: Actor,
pub killer: Option<Actor>,
} }
impl Event for OnDeath {} impl Event for OnDeath {}

View File

@ -45,88 +45,89 @@ fn on_setup(ctx: EventCtx<SimulateNpcs, OnSetup>) {
fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) { fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
let data = &mut *ctx.state.data_mut(); 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 if let Actor::Npc(npc_id) = ctx.event.actor
match npc.body { && let Some(npc) = data.npcs.get(npc_id)
Body::Humanoid(_) => { {
if let Some((site_id, site)) = data let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
.sites
.iter() // Respawn dead NPCs
.filter(|(id, site)| { match npc.body {
Some(*id) != npc.home Body::Humanoid(_) => {
&& site.faction == npc.faction if let Some((site_id, site)) = data
&& site.world_site.map_or(false, |s| { .sites
matches!(ctx.index.sites.get(s).kind, SiteKind::Refactor(_)) .iter()
}) .filter(|(id, site)| {
}) Some(*id) != npc.home
.min_by_key(|(_, site)| site.population.len()) && site.faction == npc.faction
{ && site.world_site.map_or(false, |s| {
let rand_wpos = |rng: &mut ChaChaRng| { matches!(ctx.index.sites.get(s).kind, SiteKind::Refactor(_))
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); })
wpos2d })
.map(|e| e as f32 + 0.5) .min_by_key(|(_, site)| site.population.len())
.with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) {
}; let rand_wpos = |rng: &mut ChaChaRng| {
let random_humanoid = |rng: &mut ChaChaRng| { let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap(); wpos2d
Body::Humanoid(comp::humanoid::Body::random_with(rng, species)) .map(|e| e as f32 + 0.5)
}; .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
data.spawn_npc( };
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) let random_humanoid = |rng: &mut ChaChaRng| {
.with_personality(Personality::random(&mut rng)) let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
.with_home(site_id) Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
.with_faction(npc.faction) };
.with_profession(npc.profession.clone()), data.spawn_npc(
); Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
} else { .with_personality(Personality::random(&mut rng))
warn!("No site found for respawning humaniod"); .with_home(site_id)
} .with_faction(npc.faction)
}, .with_profession(npc.profession.clone()),
Body::BirdLarge(_) => { );
if let Some((site_id, site)) = data } else {
.sites warn!("No site found for respawning humaniod");
.iter() }
.filter(|(id, site)| { },
Some(*id) != npc.home Body::BirdLarge(_) => {
&& site.world_site.map_or(false, |s| { if let Some((site_id, site)) = data
matches!(ctx.index.sites.get(s).kind, SiteKind::Dungeon(_)) .sites
}) .iter()
}) .filter(|(id, site)| {
.min_by_key(|(_, site)| site.population.len()) Some(*id) != npc.home
{ && site.world_site.map_or(false, |s| {
let rand_wpos = |rng: &mut ChaChaRng| { matches!(ctx.index.sites.get(s).kind, SiteKind::Dungeon(_))
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); })
wpos2d })
.map(|e| e as f32 + 0.5) .min_by_key(|(_, site)| site.population.len())
.with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) {
}; let rand_wpos = |rng: &mut ChaChaRng| {
let species = [ let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
comp::body::bird_large::Species::Phoenix, wpos2d
comp::body::bird_large::Species::Cockatrice, .map(|e| e as f32 + 0.5)
comp::body::bird_large::Species::Roc, .with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
] };
.choose(&mut rng) let species = [
.unwrap(); comp::body::bird_large::Species::Phoenix,
data.npcs.create_npc( comp::body::bird_large::Species::Cockatrice,
Npc::new( comp::body::bird_large::Species::Roc,
rng.gen(), ]
rand_wpos(&mut rng), .choose(&mut rng)
Body::BirdLarge(comp::body::bird_large::Body::random_with( .unwrap();
&mut rng, species, data.npcs.create_npc(
)), Npc::new(
) rng.gen(),
.with_home(site_id), rand_wpos(&mut rng),
); Body::BirdLarge(comp::body::bird_large::Body::random_with(
} else { &mut rng, species,
warn!("No site found for respawning bird"); )),
} )
}, .with_home(site_id),
_ => unimplemented!(), );
} else {
warn!("No site found for respawning bird");
}
},
_ => unimplemented!(),
}
} }
} }

View File

@ -2,7 +2,7 @@ use crate::{
event::{EventCtx, OnDeath, OnSetup, OnTick}, event::{EventCtx, OnDeath, OnSetup, OnTick},
RtState, Rule, RuleError, RtState, Rule, RuleError,
}; };
use common::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize}; use common::{grid::Grid, rtsim::Actor, terrain::TerrainChunkSize, vol::RectVolSize};
pub struct SyncNpcs; pub struct SyncNpcs;
@ -33,14 +33,20 @@ fn on_setup(ctx: EventCtx<SyncNpcs, OnSetup>) {
fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) { fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
let data = &mut *ctx.state.data_mut(); let data = &mut *ctx.state.data_mut();
// Remove NPC from home population if let Actor::Npc(npc_id) = ctx.event.actor {
if let Some(home) = data // Remove NPC from home population
.npcs if let Some(home) = data
.get(ctx.event.npc_id) .npcs
.and_then(|npc| npc.home) .get(npc_id)
.and_then(|home| data.sites.get_mut(home)) .and_then(|npc| npc.home)
{ .and_then(|home| data.sites.get_mut(home))
home.population.remove(&ctx.event.npc_id); {
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);
} }
} }

View File

@ -42,6 +42,7 @@ use common::{
outcome::Outcome, outcome::Outcome,
parse_cmd_args, parse_cmd_args,
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay}, resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay},
rtsim::Actor,
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize}, terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
vol::ReadVol, vol::ReadVol,
@ -2090,10 +2091,11 @@ fn handle_kill_npcs(
.copied() .copied()
{ {
ecs.write_resource::<crate::rtsim::RtSim>() ecs.write_resource::<crate::rtsim::RtSim>()
.hook_rtsim_entity_delete( .hook_rtsim_actor_death(
&ecs.read_resource::<Arc<world::World>>(), &ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<world::IndexOwned>().as_index_ref(), ecs.read_resource::<world::IndexOwned>().as_index_ref(),
rtsim_entity, Actor::Npc(rtsim_entity.0),
None,
); );
} }
Some(entity) Some(entity)

View File

@ -26,7 +26,6 @@ use common::{
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
outcome::{HealthChangeInfo, Outcome}, outcome::{HealthChangeInfo, Outcome},
resources::{Secs, Time}, resources::{Secs, Time},
rtsim::RtSimEntity,
states::utils::StageSection, states::utils::StageSection,
terrain::{Block, BlockKind, TerrainGrid}, terrain::{Block, BlockKind, TerrainGrid},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
@ -519,23 +518,31 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
} }
if should_delete { if should_delete {
if let Some(rtsim_entity) = state if let Some(actor) = state.entity_as_actor(entity) {
.ecs()
.read_storage::<RtSimEntity>()
.get(entity)
.copied()
{
state state
.ecs() .ecs()
.write_resource::<rtsim::RtSim>() .write_resource::<rtsim::RtSim>()
.hook_rtsim_entity_delete( .hook_rtsim_actor_death(
&state.ecs().read_resource::<Arc<world::World>>(), &state.ecs().read_resource::<Arc<world::World>>(),
state state
.ecs() .ecs()
.read_resource::<world::IndexOwned>() .read_resource::<world::IndexOwned>()
.as_index_ref(), .as_index_ref(),
rtsim_entity, actor,
); last_change
.by
.as_ref()
.and_then(
|(DamageContributor::Solo(entity_uid)
| DamageContributor::Group { entity_uid, .. })| {
state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal((*entity_uid).into())
},
)
.and_then(|killer| state.entity_as_actor(killer)),
);
} }
if let Err(e) = state.delete_entity_recorded(entity) { if let Err(e) = state.delete_entity_recorded(entity) {

View File

@ -5,7 +5,7 @@ pub mod tick;
use atomicwrites::{AtomicFile, OverwriteBehavior}; use atomicwrites::{AtomicFile, OverwriteBehavior};
use common::{ use common::{
grid::Grid, grid::Grid,
rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings}, rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
terrain::Block, terrain::Block,
}; };
use common_ecs::dispatch; use common_ecs::dispatch;
@ -164,15 +164,14 @@ impl RtSim {
} }
} }
pub fn hook_rtsim_entity_delete( pub fn hook_rtsim_actor_death(
&mut self, &mut self,
world: &World, world: &World,
index: IndexRef, index: IndexRef,
entity: RtSimEntity, actor: Actor,
killer: Option<Actor>,
) { ) {
// Should entity deletion be death? They're not exactly the same thing... self.state.emit(OnDeath { actor, killer }, world, index);
self.state.emit(OnDeath { npc_id: entity.0 }, world, index);
self.state.data_mut().npcs.remove(entity.0);
} }
pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) { pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) {

View File

@ -25,6 +25,7 @@ use common::{
link::{Link, LinkHandle}, link::{Link, LinkHandle},
mounting::Mounting, mounting::Mounting,
resources::{Secs, Time, TimeOfDay}, resources::{Secs, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity},
slowjob::SlowJobPool, slowjob::SlowJobPool,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
LoadoutBuilder, ViewDistances, LoadoutBuilder, ViewDistances,
@ -139,6 +140,8 @@ pub trait StateExt {
&mut self, &mut self,
entity: EcsEntity, entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration>; ) -> Result<(), specs::error::WrongGeneration>;
/// Get the given entity as an [`Actor`], if it is one.
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor>;
} }
impl StateExt for State { impl StateExt for State {
@ -1103,6 +1106,26 @@ impl StateExt for State {
} }
res res
} }
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
if let Some(rtsim_entity) = self
.ecs()
.read_storage::<RtSimEntity>()
.get(entity)
.copied()
{
Some(Actor::Npc(rtsim_entity.0))
} else if let Some(PresenceKind::Character(character)) = self
.ecs()
.read_storage::<Presence>()
.get(entity)
.map(|p| p.kind)
{
Some(Actor::Character(character))
} else {
None
}
}
} }
fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) { fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {