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 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<Actor>,
}
impl Event for OnDeath {}

View File

@ -45,88 +45,89 @@ fn on_setup(ctx: EventCtx<SimulateNpcs, OnSetup>) {
fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
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!(),
}
}
}

View File

@ -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<SyncNpcs, OnSetup>) {
fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
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);
}
}

View File

@ -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::<crate::rtsim::RtSim>()
.hook_rtsim_entity_delete(
.hook_rtsim_actor_death(
&ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
rtsim_entity,
Actor::Npc(rtsim_entity.0),
None,
);
}
Some(entity)

View File

@ -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::<RtSimEntity>()
.get(entity)
.copied()
{
if let Some(actor) = state.entity_as_actor(entity) {
state
.ecs()
.write_resource::<rtsim::RtSim>()
.hook_rtsim_entity_delete(
&state.ecs().read_resource::<Arc<world::World>>(),
state
.ecs()
.read_resource::<world::IndexOwned>()
.as_index_ref(),
rtsim_entity,
);
.hook_rtsim_actor_death(
&state.ecs().read_resource::<Arc<world::World>>(),
state
.ecs()
.read_resource::<world::IndexOwned>()
.as_index_ref(),
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) {

View File

@ -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<Actor>,
) {
// 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) {

View File

@ -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<Actor>;
}
impl StateExt for State {
@ -1103,6 +1106,26 @@ impl StateExt for State {
}
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) {