1pub mod faction;
2pub mod name;
3pub mod site;
4
5use crate::data::{
6 CURRENT_VERSION, Data, Nature,
7 airship::AirshipSpawningLocation,
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::{BiomeKind, CoordinateConversions, TerrainChunkSize},
17 vol::RectVolSize,
18};
19use rand::prelude::*;
20use rand_chacha::ChaChaRng;
21use tracing::info;
22use vek::*;
23use world::{
24 CONFIG, IndexRef, World,
25 civ::airship_travel::{AirshipDockingSide, Airships},
26 site::SiteKind,
27 site2::{PlotKind, plot::PlotKindMeta},
28 util::seed_expan,
29};
30
31impl Data {
32 pub fn generate(settings: &WorldSettings, world: &World, index: IndexRef) -> Self {
33 let mut seed = [0; 32];
34 seed.iter_mut()
35 .zip(&mut index.seed.to_le_bytes())
36 .for_each(|(dst, src)| *dst = *src);
37 let mut rng = SmallRng::from_seed(seed);
38
39 let mut this = Self {
40 version: CURRENT_VERSION,
41 nature: Nature::generate(world),
42 npcs: Npcs::default(),
43 sites: Default::default(),
44 factions: Default::default(),
45 reports: Default::default(),
46 airship_sim: Default::default(),
47 architect: Default::default(),
48
49 tick: 0,
50 time_of_day: TimeOfDay(settings.start_time),
51 should_purge: false,
52 };
53
54 let initial_factions = (0..16)
55 .map(|_| {
56 let faction = Faction::generate(world, index, &mut rng);
57 let wpos = world
58 .sim()
59 .get_size()
60 .map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
61 rng.gen_range(0..(e * sz) as i32)
62 });
63 (wpos, this.factions.create(faction))
64 })
65 .collect::<Vec<_>>();
66 info!("Generated {} rtsim factions.", this.factions.len());
67
68 for (world_site_id, _) in index.sites.iter() {
70 let site = Site::generate(
71 world_site_id,
72 world,
73 index,
74 &initial_factions,
75 &this.factions,
76 &mut rng,
77 );
78 this.sites.create(site);
79 }
80 info!(
81 "Registering {} rtsim sites from world sites.",
82 this.sites.len()
83 );
84
85 let random_humanoid = |rng: &mut SmallRng| {
86 let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
87 Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
88 };
89
90 for (site_id, site, site2) in this.sites.iter()
92 .filter_map(|(site_id, site)| Some((site_id, site, site.world_site
94 .and_then(|ws| match &index.sites.get(ws).kind {
95 SiteKind::Refactor(site2)
96 | SiteKind::CliffTown(site2)
97 | SiteKind::SavannahTown(site2)
98 | SiteKind::CoastalTown(site2)
99 | SiteKind::DesertCity(site2) => Some(site2),
100 _ => None,
101 })?)))
102 {
103 let Some(good_or_evil) = site
104 .faction
105 .and_then(|f| this.factions.get(f))
106 .map(|f| f.good_or_evil)
107 else {
108 continue;
109 };
110
111 let rand_wpos = |rng: &mut SmallRng, matches_plot: fn(&PlotKind) -> bool| {
112 let wpos2d = site2
113 .plots()
114 .filter(|plot| matches_plot(plot.kind()))
115 .choose(&mut thread_rng())
116 .map(|plot| site2.tile_center_wpos(plot.root_tile()))
117 .unwrap_or_else(|| site.wpos.map(|e| e + rng.gen_range(-10..10)));
118 wpos2d
119 .map(|e| e as f32 + 0.5)
120 .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
121 };
122 let matches_buildings = (|kind: &PlotKind| {
123 matches!(
124 kind,
125 PlotKind::House(_)
126 | PlotKind::Workshop(_)
127 | PlotKind::AirshipDock(_)
128 | PlotKind::Tavern(_)
129 | PlotKind::Plaza(_)
130 | PlotKind::SavannahAirshipDock(_)
131 | PlotKind::SavannahHut(_)
132 | PlotKind::SavannahWorkshop(_)
133 | PlotKind::CliffTower(_)
134 | PlotKind::DesertCityMultiPlot(_)
135 | PlotKind::DesertCityTemple(_)
136 | PlotKind::CoastalHouse(_)
137 | PlotKind::CoastalWorkshop(_)
138 )
139 }) as _;
140 let matches_plazas = (|kind: &PlotKind| matches!(kind, PlotKind::Plaza(_))) as _;
141 if good_or_evil {
142 for _ in 0..site2.plots().len() {
143 this.npcs.create_npc(
144 Npc::new(
145 rng.gen(),
146 rand_wpos(&mut rng, matches_buildings),
147 random_humanoid(&mut rng),
148 Role::Civilised(Some(match rng.gen_range(0..20) {
149 0 => Profession::Hunter,
150 1 => Profession::Blacksmith,
151 2 => Profession::Chef,
152 3 => Profession::Alchemist,
153 5..=8 => Profession::Farmer,
154 9..=10 => Profession::Herbalist,
155 11..=16 => Profession::Guard,
156 _ => Profession::Adventurer(rng.gen_range(0..=3)),
157 })),
158 )
159 .with_faction(site.faction)
160 .with_home(site_id)
161 .with_personality(Personality::random(&mut rng)),
162 );
163 }
164 } else {
165 for _ in 0..15 {
166 this.npcs.create_npc(
167 Npc::new(
168 rng.gen(),
169 rand_wpos(&mut rng, matches_buildings),
170 random_humanoid(&mut rng),
171 Role::Civilised(Some(Profession::Cultist)),
172 )
173 .with_personality(Personality::random_evil(&mut rng))
174 .with_faction(site.faction)
175 .with_home(site_id),
176 );
177 }
178 }
179 if good_or_evil {
181 for _ in 0..(site2.plots().len() / 6) + 1 {
182 this.npcs.create_npc(
183 Npc::new(
184 rng.gen(),
185 rand_wpos(&mut rng, matches_plazas),
186 random_humanoid(&mut rng),
187 Role::Civilised(Some(Profession::Merchant)),
188 )
189 .with_home(site_id)
190 .with_personality(Personality::random_good(&mut rng)),
191 );
192 }
193 }
194 }
195
196 let spawning_locations = this.airship_spawning_locations(world, index);
201
202 let mut airship_rng = ChaChaRng::from_seed(seed_expan::rng_state(index.index.seed));
212 for spawning_location in spawning_locations.iter() {
213 this.spawn_airship(spawning_location, &mut airship_rng);
214 }
215
216 for (site_id, site) in this.sites.iter() {
218 let rand_wpos = |rng: &mut SmallRng| {
219 let spread_factor = rng.gen_range(-3..3) * 50;
221 let spread = if spread_factor == 0 {
222 100
223 } else {
224 spread_factor
225 };
226 let wpos2d = site.wpos.map(|e| e + spread);
227 wpos2d
228 .map(|e| e as f32 + 0.5)
229 .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
230 };
231 let site_kind = site.world_site.map(|ws| &index.sites.get(ws).kind);
232 let Some(species) = [
233 Some(comp::body::bird_large::Species::Phoenix)
234 .filter(|_| matches!(site_kind, Some(SiteKind::DwarvenMine(_)))),
235 Some(comp::body::bird_large::Species::Cockatrice)
236 .filter(|_| matches!(site_kind, Some(SiteKind::Myrmidon(_)))),
237 Some(comp::body::bird_large::Species::Roc)
238 .filter(|_| matches!(site_kind, Some(SiteKind::Haniwa(_)))),
239 Some(comp::body::bird_large::Species::FlameWyvern)
240 .filter(|_| matches!(site_kind, Some(SiteKind::Terracotta(_)))),
241 Some(comp::body::bird_large::Species::CloudWyvern)
242 .filter(|_| matches!(site_kind, Some(SiteKind::Sahagin(_)))),
243 Some(comp::body::bird_large::Species::FrostWyvern)
244 .filter(|_| matches!(site_kind, Some(SiteKind::Adlet(_)))),
245 Some(comp::body::bird_large::Species::SeaWyvern)
246 .filter(|_| matches!(site_kind, Some(SiteKind::ChapelSite(_)))),
247 Some(comp::body::bird_large::Species::WealdWyvern)
248 .filter(|_| matches!(site_kind, Some(SiteKind::GiantTree(_)))),
249 ]
250 .into_iter()
251 .flatten()
252 .choose(&mut rng) else {
253 continue;
254 };
255
256 this.npcs.create_npc(
257 Npc::new(
258 rng.gen(),
259 rand_wpos(&mut rng),
260 Body::BirdLarge(comp::body::bird_large::Body::random_with(
261 &mut rng, &species,
262 )),
263 Role::Wild,
264 )
265 .with_home(site_id),
266 );
267 }
268
269 for _ in 0..(world.sim().map_size_lg().chunks_len() / 2usize.pow(13)).clamp(5, 1000) {
271 if let Some((wpos, chunk)) = (0..10)
273 .map(|_| world.sim().get_size().map(|sz| rng.gen_range(0..sz as i32)))
274 .find_map(|pos| Some((pos, world.sim().get(pos).filter(|c| !c.is_underwater())?)))
275 .map(|(pos, chunk)| {
276 let wpos2d = pos.cpos_to_wpos_center();
277 (
278 wpos2d
279 .map(|e| e as f32 + 0.5)
280 .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)),
281 chunk,
282 )
283 })
284 {
285 let biome = chunk.get_biome();
286 let Some(species) = [
287 Some(comp::body::biped_large::Species::Ogre),
288 Some(comp::body::biped_large::Species::Cyclops),
289 Some(comp::body::biped_large::Species::Wendigo)
290 .filter(|_| biome == BiomeKind::Taiga),
291 Some(comp::body::biped_large::Species::Cavetroll),
292 Some(comp::body::biped_large::Species::Mountaintroll)
293 .filter(|_| biome == BiomeKind::Mountain),
294 Some(comp::body::biped_large::Species::Swamptroll)
295 .filter(|_| biome == BiomeKind::Swamp),
296 Some(comp::body::biped_large::Species::Blueoni),
297 Some(comp::body::biped_large::Species::Redoni),
298 Some(comp::body::biped_large::Species::Tursus)
299 .filter(|_| chunk.temp < CONFIG.snow_temp),
300 ]
301 .into_iter()
302 .flatten()
303 .choose(&mut rng) else {
304 continue;
305 };
306
307 this.npcs.create_npc(Npc::new(
308 rng.gen(),
309 wpos,
310 Body::BipedLarge(comp::body::biped_large::Body::random_with(
311 &mut rng, &species,
312 )),
313 Role::Monster,
314 ));
315 }
316 }
317 if let Some((wpos, _)) = (0..100)
320 .map(|_| world.sim().get_size().map(|sz| rng.gen_range(0..sz as i32)))
321 .find_map(|pos| Some((pos, world.sim().get(pos).filter(|c| !c.is_underwater())?)))
322 .map(|(pos, chunk)| {
323 let wpos2d = pos.cpos_to_wpos_center();
324 (
325 wpos2d
326 .map(|e| e as f32 + 0.5)
327 .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)),
328 chunk,
329 )
330 })
331 {
332 let species = comp::body::biped_large::Species::Gigasfrost;
333
334 this.npcs.create_npc(Npc::new(
335 rng.gen(),
336 wpos,
337 Body::BipedLarge(comp::body::biped_large::Body::random_with(
338 &mut rng, &species,
339 )),
340 Role::Monster,
341 ));
342 }
343
344 info!("Generated {} rtsim NPCs.", this.npcs.len());
345
346 this
347 }
348
349 pub fn airship_spawning_locations(
354 &self,
355 world: &World,
356 index: IndexRef,
357 ) -> Vec<AirshipSpawningLocation> {
358 self.sites
359 .iter()
360 .filter_map(|(site_id, site)| {
361 Some((
362 site_id,
363 site,
364 site.world_site
365 .and_then(|ws| match &index.sites.get(ws).kind {
366 SiteKind::Refactor(site2)
367 | SiteKind::CliffTown(site2)
368 | SiteKind::SavannahTown(site2)
369 | SiteKind::CoastalTown(site2)
370 | SiteKind::DesertCity(site2) => Some(site2),
371 _ => None,
372 })?,
373 ))
374 })
375 .flat_map(|(site_id, _, site2)| {
376 site2
377 .plots
378 .values()
379 .filter_map(move |plot| {
380 if let Some(PlotKindMeta::AirshipDock {
381 center,
382 docking_positions,
383 ..
384 }) = plot.kind().meta()
385 {
386 Some(
387 docking_positions
388 .iter()
389 .filter_map(move |docking_pos| {
390 if world
391 .civs()
392 .airships
393 .should_spawn_airship_at_docking_position(
394 docking_pos,
395 site2.name(),
396 )
397 {
398 let (airship_pos, airship_dir) =
399 Airships::airship_vec_for_docking_pos(
400 docking_pos.map(|i| i as f32),
401 center.map(|i| i as f32),
402 Some(AirshipDockingSide::Starboard),
408 );
409 Some(AirshipSpawningLocation {
410 pos: airship_pos,
411 dir: airship_dir,
412 center,
413 docking_pos: *docking_pos,
414 site_id,
415 site_name: site2.name().to_string(),
416 })
417 } else {
418 None
419 }
420 })
421 .collect::<Vec<_>>(),
422 )
423 } else {
424 None
425 }
426 })
427 .flatten()
428 })
429 .collect::<Vec<_>>()
430 }
431
432 pub fn spawn_airship(
436 &mut self,
437 spawning_location: &AirshipSpawningLocation,
438 rng: &mut impl Rng,
439 ) -> (NpcId, NpcId) {
440 let vehicle_id = self.npcs.create_npc(Npc::new(
441 rng.gen(),
442 spawning_location.pos,
443 Body::Ship(comp::body::ship::Body::DefaultAirship),
444 Role::Vehicle,
445 ));
446 let airship = self.npcs.get_mut(vehicle_id).unwrap();
447 let airship_mount_offset = airship.body.mount_offset();
448
449 let captain_pos = spawning_location.pos
450 + Vec3::new(
451 spawning_location.dir.x * airship_mount_offset.x,
452 spawning_location.dir.y * airship_mount_offset.y,
453 airship_mount_offset.z,
454 );
455 let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
456 let npc_id = self.npcs.create_npc(
457 Npc::new(
458 rng.gen(),
459 captain_pos,
460 Body::Humanoid(comp::humanoid::Body::random_with(rng, species)),
461 Role::Civilised(Some(Profession::Captain)),
462 )
463 .with_personality(Personality::random_good(rng)),
465 );
466 self.npcs.get_mut(npc_id).unwrap().dir = spawning_location.dir.xy().normalized();
468
469 self.npcs
471 .mounts
472 .steer(vehicle_id, npc_id)
473 .expect("We just created these npcs!");
474
475 self.npcs.get_mut(vehicle_id).unwrap().dir = spawning_location.dir.xy().normalized();
476
477 (npc_id, vehicle_id)
478 }
479}