veloren_rtsim/gen/
mod.rs

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    // Spawn some npcs at settlements
32    for (_, site) in sites.iter()
33        // TODO: Stupid. Only find site towns
34        .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    // Pirates
57    pop.add(TrackedPopulation::PirateCaptains, pirate_hideouts);
58    pop.add(TrackedPopulation::Pirates, 10 * pirate_hideouts);
59
60    // Birds
61    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    // Monsters
74    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        // Register sites with rtsim
122        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        // Airships
139        // Get the spawning locations for airships.
140        let spawning_locations = world.civs().airships.airship_spawning_locations();
141
142        // When generating rtsim data from scratch, put an airship (and captain) at each
143        // available spawning location. Note this is just to get the initial
144        // airship NPCs created. Since the airship route data is not persisted,
145        // but the NPCs themselves are, the rtsim data contains airships and captains,
146        // but not the routes, and the information about routes and route
147        // assignments is generated each time the server is started. This process
148        // of resolving the rtsim data to the world data is done in the `migrate`
149        // module.
150        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    /// Creates an airship and captain NPC. The NPCs are created at the
170    /// approximate correct position, but the final 3D position is set
171    /// in register_airship_captain, which is called after all the airship
172    /// NPCs are created or loaded from the rtsim data.
173    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_home(spawning_location.site_id)
196            .with_personality(Personality::random_good(rng)),
197        );
198
199        // The captain is mounted on the airship
200        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}