use crate::{
event::{EventCtx, OnDeath, OnHealthChange, OnSetup, OnTick},
RtState, Rule, RuleError,
};
use common::{
grid::Grid,
rtsim::{Actor, NpcInput},
terrain::CoordinateConversions,
};
pub struct SyncNpcs;
impl Rule for SyncNpcs {
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
rtstate.bind::<Self, OnSetup>(on_setup);
rtstate.bind::<Self, OnDeath>(on_death);
rtstate.bind::<Self, OnHealthChange>(on_health_change);
rtstate.bind::<Self, OnTick>(on_tick);
Ok(Self)
}
}
fn on_setup(ctx: EventCtx<SyncNpcs, OnSetup>) {
let data = &mut *ctx.state.data_mut();
data.npcs.npc_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
for (npc_id, npc) in data.npcs.npcs.iter() {
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
home.population.insert(npc_id);
}
}
let sites_iter = data.sites.iter().filter_map(|(site_id, site)| {
let site2 = site
.world_site
.and_then(|ws| ctx.index.sites.get(ws).site2())?;
Some((site_id, site, site2))
});
let nearest_by_size = sites_iter.clone()
.map(|(site_id, site, site2)| {
let mut other_sites = sites_iter.clone()
.filter(|(other_id, _, other_site2)| *other_id != site_id && other_site2.plots().len() > site2.plots().len())
.collect::<Vec<_>>();
other_sites.sort_by_key(|(_, other, _)| other.wpos.as_::<i64>().distance_squared(site.wpos.as_::<i64>()));
let mut max_size = 0;
other_sites.retain(|(_, _, other_site2)| {
if other_site2.plots().len() > max_size {
max_size = other_site2.plots().len();
true
} else {
false
}
});
let nearest_by_size = other_sites
.into_iter()
.map(|(site_id, _, _)| site_id)
.collect::<Vec<_>>();
(site_id, nearest_by_size)
})
.collect::<Vec<_>>();
for (site_id, nearest_by_size) in nearest_by_size {
if let Some(site) = data.sites.get_mut(site_id) {
site.nearby_sites_by_size = nearest_by_size;
}
}
}
fn on_health_change(ctx: EventCtx<SyncNpcs, OnHealthChange>) {
let data = &mut *ctx.state.data_mut();
if ctx.event.new_health_fraction != 0.0
&& let Actor::Npc(npc_id) = ctx.event.actor
{
if let Some(npc) = data.npcs.get_mut(npc_id) {
npc.health_fraction = ctx.event.new_health_fraction;
}
}
}
fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
let data = &mut *ctx.state.data_mut();
if let Actor::Npc(npc_id) = ctx.event.actor {
if let Some(npc) = data.npcs.get_mut(npc_id) {
npc.health_fraction = 0.0;
}
}
}
fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
let data = &mut *ctx.state.data_mut();
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
npc.current_site = ctx
.world
.sim()
.get(npc.wpos.xy().as_().wpos_to_cpos())
.and_then(|chunk| {
chunk
.sites
.iter()
.find_map(|site| data.sites.world_site_map.get(site).copied())
});
if let Some(current_site) = npc.current_site
&& Some(current_site) == npc.home
{
if let Some(site) = data.sites.get_mut(current_site) {
site.known_reports.extend(npc.known_reports.iter().copied());
npc.inbox.extend(
site.known_reports
.iter()
.copied()
.filter(|report| !npc.known_reports.contains(report))
.map(NpcInput::Report),
);
}
}
let chunk_pos = npc.wpos.xy().as_().wpos_to_cpos();
if npc.chunk_pos != Some(chunk_pos) {
if let Some(cell) = npc
.chunk_pos
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
{
if let Some(index) = cell.npcs.iter().position(|id| *id == npc_id) {
cell.npcs.swap_remove(index);
}
}
npc.chunk_pos = Some(chunk_pos);
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
cell.npcs.push(npc_id);
}
}
}
}