Skip to main content

veloren_rtsim/rule/
sync_npcs.rs

1use crate::{
2    RtState, Rule, RuleError,
3    event::{EventCtx, OnDeath, OnHealthChange, OnSetup, OnTick},
4};
5use common::{grid::Grid, rtsim::NpcInput, terrain::CoordinateConversions};
6
7pub struct SyncNpcs;
8
9impl Rule for SyncNpcs {
10    fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
11        rtstate.bind::<Self, OnSetup>(on_setup);
12        rtstate.bind::<Self, OnDeath>(on_death);
13        rtstate.bind::<Self, OnHealthChange>(on_health_change);
14        rtstate.bind::<Self, OnTick>(on_tick);
15
16        Ok(Self)
17    }
18}
19
20fn on_setup(ctx: EventCtx<SyncNpcs, OnSetup>) {
21    let data = &mut *ctx.state.data_mut();
22
23    // Create actor grid
24    data.actors.actor_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
25
26    // Add actors to home population
27    for (actor_id, actor) in data.actors.iter() {
28        if let Some(home) = actor.home.and_then(|home| data.sites.get_mut(home)) {
29            home.population.insert(actor_id);
30        }
31    }
32
33    // Update the list of nearest sites by size for each site
34    let sites_iter = data.sites.iter().filter_map(|(site_id, site)| {
35        let world_site = site.world_site.map(|ws| ctx.index.sites.get(ws))?;
36        Some((site_id, site, world_site))
37    });
38    let nearest_by_size = sites_iter.clone()
39        .map(|(site_id, site, world_site)| {
40            let mut other_sites = sites_iter.clone()
41                // Only include sites in the list if they're not the current one and they're more populus
42                .filter(|(other_id, _, other_site)| *other_id != site_id && other_site.plots().len() > world_site.plots().len())
43                .collect::<Vec<_>>();
44            other_sites.sort_by_key(|(_, other, _)| other.wpos.as_::<i64>().distance_squared(site.wpos.as_::<i64>()));
45            let mut max_size = 0;
46            // Remove sites that aren't in increasing order of size (Stalin sort?!)
47            other_sites.retain(|(_, _, other_site)| {
48                if other_site.plots().len() > max_size {
49                    max_size = other_site.plots().len();
50                    true
51                } else {
52                    false
53                }
54            });
55            let nearest_by_size = other_sites
56                .into_iter()
57                .map(|(site_id, _, _)| site_id)
58                .collect::<Vec<_>>();
59            (site_id, nearest_by_size)
60        })
61        .collect::<Vec<_>>();
62    for (site_id, nearest_by_size) in nearest_by_size {
63        if let Some(site) = data.sites.get_mut(site_id) {
64            site.nearby_sites_by_size = nearest_by_size;
65        }
66    }
67}
68
69fn on_health_change(ctx: EventCtx<SyncNpcs, OnHealthChange>) {
70    let data = &mut *ctx.state.data_mut();
71
72    // As this handler does not correctly handle death, ignore events that set the
73    // health fraction to 0 (dead)
74    if ctx.event.new_health_fraction != 0.0
75        && let Some(actor) = data.actors.get_mut(ctx.event.actor)
76        && let Some(presence) = &mut actor.presence
77    {
78        presence.health_fraction = ctx.event.new_health_fraction;
79    }
80}
81
82fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
83    let data = &mut *ctx.state.data_mut();
84
85    if let Some(actor) = data.actors.get_mut(ctx.event.actor)
86        && let Some(presence) = &mut actor.presence
87    {
88        // Mark the actor as dead, allowing us to clear them up later
89        presence.health_fraction = 0.0;
90    }
91}
92
93fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
94    let data = &mut *ctx.state.data_mut();
95    for (actor_id, actor) in data.actors.actors.iter_mut() {
96        // Update the actor's current site, if any
97        actor.current_site = ctx
98            .world
99            .sim()
100            .get(actor.wpos.xy().as_().wpos_to_cpos())
101            .and_then(|chunk| {
102                chunk
103                    .sites
104                    .iter()
105                    .find_map(|site| data.sites.world_site_map.get(site).copied())
106            });
107
108        // Share known reports with current site, if it's our home
109        // TODO: Only share new reports
110        if let Some(current_site) = actor.current_site
111            && Some(current_site) == actor.home
112            && let Some(site) = data.sites.get_mut(current_site)
113            && let Some(npc) = actor.npc_mut()
114        {
115            // TODO: Sites should have an inbox and their own AI code...?
116            site.known_reports.extend(npc.known_reports.iter().copied());
117            npc.inbox.extend(
118                site.known_reports
119                    .iter()
120                    .copied()
121                    .filter(|report| !npc.known_reports.contains(report))
122                    .map(NpcInput::Report),
123            );
124        }
125
126        // Update the actor's grid cell
127        let chunk_pos = if actor.presence.is_some() {
128            Some(actor.wpos.xy().as_().wpos_to_cpos())
129        } else {
130            None
131        };
132        if actor.chunk_pos != chunk_pos {
133            if let Some(cell) = actor
134                .chunk_pos
135                .and_then(|chunk_pos| data.actors.actor_grid.get_mut(chunk_pos))
136                && let Some(index) = cell.actors.iter().position(|id| *id == actor_id)
137            {
138                cell.actors.swap_remove(index);
139            }
140            actor.chunk_pos = chunk_pos;
141            if let Some(chunk_pos) = chunk_pos
142                && let Some(cell) = data.actors.actor_grid.get_mut(chunk_pos)
143            {
144                cell.actors.push(actor_id);
145            }
146        }
147
148        // Make characters that haven't been seen since the penultimate tick be no
149        // longer present (likely because the player they represent has logged
150        // off) TODO: Prune characters that we've not seen for a *long* time
151        // once we hit some arbitrary cap
152        if let Some(character) = actor.character()
153            && let Some(last_present_at) = character.last_present_at
154            && data.tick > last_present_at + 1
155        {
156            actor.presence = None;
157        }
158    }
159}