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)
}
}