1pub mod faction;
2pub mod name;
3pub mod site;
4
5use crate::data::{
6 CURRENT_VERSION, Data, Nature,
7 architect::{Population, TrackedPopulation},
8 faction::Faction,
9 npc::{Npc, Npcs, Profession},
10 site::Site,
11};
12use common::{
13 comp::{self, Body},
14 resources::TimeOfDay,
15 rtsim::{NpcId, Personality, Role, WorldSettings},
16 terrain::TerrainChunkSize,
17 vol::RectVolSize,
18};
19use rand::prelude::*;
20use rand_chacha::ChaChaRng;
21use tracing::info;
22use world::{
23 IndexRef, World, civ::airship_travel::AirshipSpawningLocation, site::PlotKind, util::seed_expan,
24};
25
26pub fn wanted_population(world: &World, index: IndexRef) -> Population {
27 let mut pop = Population::default();
28
29 let sites = &index.sites;
30
31 for (_, site) in sites.iter()
33 .filter(|(_, site)| site.meta().is_some_and(|m| matches!(m, common::terrain::SiteKindMeta::Settlement(_))))
35 {
36 let town_pop = site.plots().len() as u32;
37 let guards = town_pop / 4;
38 let adventurers = town_pop / 5;
39 let others = town_pop.saturating_sub(guards + adventurers);
40
41 pop.add(TrackedPopulation::Guards, guards);
42 pop.add(TrackedPopulation::Adventurers, adventurers);
43 pop.add(TrackedPopulation::OtherTownNpcs, others);
44
45 pop.add(TrackedPopulation::Merchants, (town_pop / 6) + 1);
46 }
47
48 let pirate_hideouts = sites
49 .iter()
50 .flat_map(|(_, site)| {
51 site.plots()
52 .filter(|plot| matches!(plot.kind(), PlotKind::PirateHideout(_)))
53 })
54 .count() as u32;
55
56 pop.add(TrackedPopulation::PirateCaptains, pirate_hideouts);
58 pop.add(TrackedPopulation::Pirates, 10 * pirate_hideouts);
59
60 let world_area = world.sim().map_size_lg().chunks_len();
62 let bird_pop = world_area.div_ceil(2usize.pow(16)) as u32;
63 let bird_kind_pop = bird_pop.div_ceil(8);
64 pop.add(TrackedPopulation::CloudWyvern, bird_kind_pop);
65 pop.add(TrackedPopulation::FrostWyvern, bird_kind_pop);
66 pop.add(TrackedPopulation::SeaWyvern, bird_kind_pop);
67 pop.add(TrackedPopulation::FlameWyvern, bird_kind_pop);
68 pop.add(TrackedPopulation::WealdWyvern, bird_kind_pop);
69 pop.add(TrackedPopulation::Phoenix, bird_kind_pop);
70 pop.add(TrackedPopulation::Roc, bird_kind_pop);
71 pop.add(TrackedPopulation::Cockatrice, bird_kind_pop);
72
73 pop.add(TrackedPopulation::GigasFrost, 1);
75 pop.add(TrackedPopulation::GigasFire, 1);
76 pop.add(
77 TrackedPopulation::OtherMonsters,
78 (world.sim().map_size_lg().chunks_len() / 2usize.pow(13)).clamp(5, 1000) as u32,
79 );
80
81 pop
82}
83
84impl Data {
85 pub fn generate(settings: &WorldSettings, world: &World, index: IndexRef) -> Self {
86 let mut seed = [0; 32];
87 seed.iter_mut()
88 .zip(&mut index.seed.to_le_bytes())
89 .for_each(|(dst, src)| *dst = *src);
90 let mut rng = SmallRng::from_seed(seed);
91
92 let mut this = Self {
93 version: CURRENT_VERSION,
94 nature: Nature::generate(world),
95 npcs: Npcs::default(),
96 sites: Default::default(),
97 factions: Default::default(),
98 reports: Default::default(),
99 airship_sim: Default::default(),
100 architect: Default::default(),
101
102 tick: 0,
103 time_of_day: TimeOfDay(settings.start_time),
104 should_purge: false,
105 };
106
107 let initial_factions = (0..16)
108 .map(|_| {
109 let faction = Faction::generate(world, index, &mut rng);
110 let wpos = world
111 .sim()
112 .get_size()
113 .map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
114 rng.gen_range(0..(e * sz) as i32)
115 });
116 (wpos, this.factions.create(faction))
117 })
118 .collect::<Vec<_>>();
119 info!("Generated {} rtsim factions.", this.factions.len());
120
121 for (world_site_id, _) in index.sites.iter() {
123 let site = Site::generate(
124 world_site_id,
125 world,
126 index,
127 &initial_factions,
128 &this.factions,
129 &mut rng,
130 );
131 this.sites.create(site);
132 }
133 info!(
134 "Registering {} rtsim sites from world sites.",
135 this.sites.len()
136 );
137
138 let spawning_locations = world.civs().airships.airship_spawning_locations();
141
142 let mut airship_rng = ChaChaRng::from_seed(seed_expan::rng_state(index.index.seed));
151 for spawning_location in spawning_locations.iter() {
152 this.spawn_airship(spawning_location, &mut airship_rng);
153 }
154
155 this.architect.wanted_population = wanted_population(world, index);
156
157 info!(
158 "Generated {} rtsim NPCs to be spawned.",
159 this.architect.wanted_population.total()
160 );
161
162 this
163 }
164
165 pub fn airship_spawning_locations(&self, world: &World) -> Vec<AirshipSpawningLocation> {
166 world.civs().airships.airship_spawning_locations()
167 }
168
169 pub fn spawn_airship(
174 &mut self,
175 spawning_location: &AirshipSpawningLocation,
176 rng: &mut impl Rng,
177 ) -> (NpcId, NpcId) {
178 let npc_wpos3d = spawning_location.pos.with_z(spawning_location.height);
179
180 let vehicle_id = self.npcs.create_npc(Npc::new(
181 rng.gen(),
182 npc_wpos3d,
183 Body::Ship(comp::body::ship::Body::DefaultAirship),
184 Role::Vehicle,
185 ));
186
187 let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
188 let npc_id = self.npcs.create_npc(
189 Npc::new(
190 rng.gen(),
191 npc_wpos3d,
192 Body::Humanoid(comp::humanoid::Body::random_with(rng, species)),
193 Role::Civilised(Some(Profession::Captain)),
194 )
195 .with_personality(Personality::random_good(rng)),
197 );
198
199 self.npcs
201 .mounts
202 .steer(vehicle_id, npc_id)
203 .expect("We just created these npcs!");
204
205 (npc_id, vehicle_id)
206 }
207}