1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use crate::{data::Site, event::OnSetup, RtState, Rule, RuleError};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use tracing::warn;
use world::site::SiteKind;

/// This rule runs at rtsim startup and broadly acts to perform some primitive
/// migration/sanitisation in order to ensure that the state of rtsim is mostly
/// sensible.
pub struct Migrate;

impl Rule for Migrate {
    fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
        rtstate.bind::<Self, OnSetup>(|ctx| {
            let data = &mut *ctx.state.data_mut();

            let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());

            // Delete rtsim sites that don't correspond to a world site
            data.sites.sites.retain(|site_id, site| {
                if let Some((world_site_id, _)) = ctx
                    .index
                    .sites
                    .iter()
                    .find(|(_, world_site)| world_site.get_origin() == site.wpos)
                {
                    site.world_site = Some(world_site_id);
                    data.sites.world_site_map.insert(world_site_id, site_id);
                    true
                } else {
                    warn!(
                        "{:?} is no longer valid because the site it was derived from no longer \
                         exists. It will now be deleted.",
                        site_id
                    );
                    false
                }
            });

            // Generate rtsim sites for world sites that don't have a corresponding rtsim
            // site yet
            for (world_site_id, _) in ctx.index.sites.iter() {
                if !data.sites.values().any(|site| {
                    site.world_site
                        .expect("Rtsim site not assigned to world site")
                        == world_site_id
                }) {
                    warn!(
                        "{:?} is new and does not have a corresponding rtsim site. One will now \
                         be generated afresh.",
                        world_site_id
                    );
                    data.sites.create(Site::generate(
                        world_site_id,
                        ctx.world,
                        ctx.index,
                        &[],
                        &data.factions,
                        &mut rng,
                    ));
                }
            }

            // Reassign NPCs to sites if their old one was deleted. If they were already
            // homeless, no need to do anything.
            for npc in data.npcs.values_mut() {
                if let Some(home) = npc.home
                    && !data.sites.contains_key(home)
                {
                    // Choose the closest habitable site as the new home for the NPC
                    npc.home = data
                        .sites
                        .sites
                        .iter()
                        .filter(|(_, site)| {
                            // TODO: This is a bit silly, but needs to wait on the removal of site1
                            site.world_site.map_or(false, |ws| {
                                matches!(
                                    &ctx.index.sites.get(ws).kind,
                                    SiteKind::Refactor(_)
                                        | SiteKind::CliffTown(_)
                                        | SiteKind::SavannahTown(_)
                                        | SiteKind::CoastalTown(_)
                                        | SiteKind::DesertCity(_)
                                )
                            })
                        })
                        .min_by_key(|(_, site)| {
                            site.wpos.as_().distance_squared(npc.wpos.xy()) as i32
                        })
                        .map(|(site_id, _)| site_id);
                }
            }
        });

        Ok(Self)
    }
}