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 quests: Default::default(),
102
103 tick: 0,
104 time_of_day: TimeOfDay(settings.start_time),
105 should_purge: false,
106 };
107
108 let initial_factions = (0..16)
109 .map(|_| {
110 let faction = Faction::generate(world, index, &mut rng);
111 let wpos = world
112 .sim()
113 .get_size()
114 .map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
115 rng.random_range(0..(e * sz) as i32)
116 });
117 (wpos, this.factions.create(faction))
118 })
119 .collect::<Vec<_>>();
120 info!("Generated {} rtsim factions.", this.factions.len());
121
122 for (world_site_id, _) in index.sites.iter() {
124 let site = Site::generate(
125 world_site_id,
126 world,
127 index,
128 &initial_factions,
129 &this.factions,
130 &mut rng,
131 );
132 this.sites.create(site);
133 }
134 info!(
135 "Registering {} rtsim sites from world sites.",
136 this.sites.len()
137 );
138
139 let spawning_locations = world.civs().airships.airship_spawning_locations();
142
143 let mut airship_rng = ChaChaRng::from_seed(seed_expan::rng_state(index.index.seed));
152 for spawning_location in spawning_locations.iter() {
153 this.spawn_airship(spawning_location, &mut airship_rng);
154 }
155
156 this.architect.wanted_population = wanted_population(world, index);
157
158 info!(
159 "Generated {} rtsim NPCs to be spawned.",
160 this.architect.wanted_population.total()
161 );
162
163 this
164 }
165
166 pub fn airship_spawning_locations(&self, world: &World) -> Vec<AirshipSpawningLocation> {
167 world.civs().airships.airship_spawning_locations()
168 }
169
170 pub fn spawn_airship(
175 &mut self,
176 spawning_location: &AirshipSpawningLocation,
177 rng: &mut impl Rng,
178 ) -> (NpcId, NpcId) {
179 let npc_wpos3d = spawning_location.pos.with_z(spawning_location.height);
180
181 let vehicle_id = self.npcs.create_npc(Npc::new(
182 rng.random(),
183 npc_wpos3d,
184 Body::Ship(comp::body::ship::Body::DefaultAirship),
185 Role::Vehicle,
186 ));
187
188 let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
189 let npc_id = self.npcs.create_npc(
190 Npc::new(
191 rng.random(),
192 npc_wpos3d,
193 Body::Humanoid(comp::humanoid::Body::random_with(rng, species)),
194 Role::Civilised(Some(Profession::Captain)),
195 )
196 .with_personality(Personality::random_good(rng)),
198 );
199
200 self.npcs
202 .mounts
203 .steer(vehicle_id, npc_id)
204 .expect("We just created these npcs!");
205
206 (npc_id, vehicle_id)
207 }
208}