veloren_world/site/
castle.rs

1use super::SpawnRules;
2use crate::{
3    IndexRef,
4    column::ColumnSample,
5    sim::WorldSim,
6    site::{
7        namegen::NameGen,
8        settlement::building::{
9            Archetype, Ori,
10            archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor},
11        },
12    },
13};
14use common::{
15    generation::ChunkSupplement,
16    terrain::{Block, BlockKind, SpriteKind},
17    vol::{ReadVol, RectSizedVol, WriteVol},
18};
19use core::f32;
20use rand::prelude::*;
21use serde::Deserialize;
22use vek::*;
23
24struct Keep {
25    offset: Vec2<i32>,
26    locus: i32,
27    storeys: i32,
28    is_tower: bool,
29    alt: i32,
30}
31
32struct Tower {
33    offset: Vec2<i32>,
34    alt: i32,
35}
36
37pub struct Castle {
38    name: String,
39    origin: Vec2<i32>,
40    //seed: u32,
41    radius: i32,
42    towers: Vec<Tower>,
43    keeps: Vec<Keep>,
44    rounded_towers: bool,
45    ridged: bool,
46    flags: bool,
47
48    evil: bool,
49}
50
51pub struct GenCtx<'a, R: Rng> {
52    sim: Option<&'a mut WorldSim>,
53    rng: &'a mut R,
54}
55
56#[derive(Deserialize)]
57pub struct Colors;
58
59impl Castle {
60    pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
61        let ctx = GenCtx { sim, rng };
62
63        let boundary_towers = ctx.rng.gen_range(5..10);
64        let keep_count = ctx.rng.gen_range(1..4);
65        let boundary_noise = ctx.rng.gen_range(-2i32..8).max(1) as f32;
66
67        let radius = 150;
68
69        let this = Self {
70            name: {
71                let name = NameGen::location(ctx.rng).generate();
72                match ctx.rng.gen_range(0..6) {
73                    0 => format!("Fort {}", name),
74                    1 => format!("{} Citadel", name),
75                    2 => format!("{} Castle", name),
76                    3 => format!("{} Stronghold", name),
77                    4 => format!("{} Fortress", name),
78                    _ => format!("{} Keep", name),
79                }
80            },
81            origin: wpos,
82            // alt: ctx
83            //     .sim
84            //     .as_ref()
85            //     .and_then(|sim| sim.get_alt_approx(wpos))
86            //     .unwrap_or(0.0) as i32
87            //     + 6,
88            //seed: ctx.rng.gen(),
89            radius,
90
91            towers: (0..boundary_towers)
92                .map(|i| {
93                    let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
94                    let dir = Vec2::new(angle.cos(), angle.sin());
95                    let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0;
96
97                    let mut offset = (dir * dist).map(|e| e as i32);
98                    // Try to move the tower around until it's not intersecting a path
99                    for i in (1..80).step_by(5) {
100                        if ctx
101                            .sim
102                            .as_ref()
103                            .and_then(|sim| sim.get_nearest_path(wpos + offset))
104                            .map(|(dist, _, _, _)| dist > 24.0)
105                            .unwrap_or(true)
106                        {
107                            break;
108                        }
109                        offset = (dir * dist)
110                            .map(|e| (e + ctx.rng.gen_range(-1.0..1.0) * i as f32) as i32);
111                    }
112
113                    Tower {
114                        offset,
115                        alt: ctx
116                            .sim
117                            .as_ref()
118                            .and_then(|sim| sim.get_alt_approx(wpos + offset))
119                            .unwrap_or(0.0) as i32
120                            + 2,
121                    }
122                })
123                .collect(),
124            rounded_towers: ctx.rng.gen(),
125            ridged: ctx.rng.gen(),
126            flags: ctx.rng.gen(),
127            evil: ctx.rng.gen(),
128            keeps: (0..keep_count)
129                .map(|i| {
130                    let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0;
131                    let dir = Vec2::new(angle.cos(), angle.sin());
132                    let dist =
133                        (radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3;
134
135                    let locus = ctx.rng.gen_range(20..26);
136                    let offset = (dir * dist).map(|e| e as i32);
137                    let storeys = ctx.rng.gen_range(1..8).clamped(3, 5);
138
139                    Keep {
140                        offset,
141                        locus,
142                        storeys,
143                        is_tower: true,
144                        alt: ctx
145                            .sim
146                            .as_ref()
147                            .and_then(|sim| sim.get_alt_approx(wpos + offset))
148                            .unwrap_or(0.0) as i32
149                            + 2,
150                    }
151                })
152                .collect(),
153        };
154
155        this
156    }
157
158    pub fn name(&self) -> &str { &self.name }
159
160    pub fn contains_point(&self, wpos: Vec2<i32>) -> bool {
161        let lpos = wpos - self.origin;
162        for i in 0..self.towers.len() {
163            let tower0 = &self.towers[i];
164            let tower1 = &self.towers[(i + 1) % self.towers.len()];
165
166            if lpos.determine_side(Vec2::zero(), tower0.offset) > 0
167                && lpos.determine_side(Vec2::zero(), tower1.offset) <= 0
168                && lpos.determine_side(tower0.offset, tower1.offset) > 0
169            {
170                return true;
171            }
172        }
173
174        false
175    }
176
177    pub fn get_origin(&self) -> Vec2<i32> { self.origin }
178
179    pub fn radius(&self) -> f32 { 200.0 }
180
181    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
182        SpawnRules {
183            trees: wpos.distance_squared(self.origin) > self.radius.pow(2),
184            ..SpawnRules::default()
185        }
186    }
187
188    pub fn apply_to<'a>(
189        &'a self,
190        index: IndexRef,
191        wpos2d: Vec2<i32>,
192        mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
193        vol: &mut (impl RectSizedVol<Vox = Block> + ReadVol + WriteVol),
194    ) {
195        for y in 0..vol.size_xy().y as i32 {
196            for x in 0..vol.size_xy().x as i32 {
197                let offs = Vec2::new(x, y);
198
199                let wpos2d = wpos2d + offs;
200                let rpos = wpos2d - self.origin;
201
202                if rpos.magnitude_squared() > (self.radius + 64).pow(2) {
203                    continue;
204                }
205
206                let col_sample = if let Some(col) = get_column(offs) {
207                    col
208                } else {
209                    continue;
210                };
211
212                // Inner ground
213                if self.contains_point(wpos2d) {
214                    let surface_z = col_sample.alt as i32;
215                    for z in -5..3 {
216                        let pos = Vec3::new(offs.x, offs.y, surface_z + z);
217
218                        if z > 0 {
219                            if vol.get(pos).unwrap().kind() != BlockKind::Water {
220                                // TODO: Take environment into account.
221                                let _ = vol.set(pos, Block::air(SpriteKind::Empty));
222                            }
223                        } else {
224                            let _ = vol.set(
225                                pos,
226                                Block::new(
227                                    BlockKind::Earth,
228                                    col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
229                                ),
230                            );
231                        }
232                    }
233                }
234
235                let (wall_dist, wall_pos, wall_alt, wall_ori, _towers) = (0..self.towers.len())
236                    .map(|i| {
237                        let tower0 = &self.towers[i];
238                        let tower1 = &self.towers[(i + 1) % self.towers.len()];
239
240                        let wall = LineSegment2 {
241                            start: tower0.offset.map(|e| e as f32),
242                            end: tower1.offset.map(|e| e as f32),
243                        };
244
245                        let projected = wall
246                            .projected_point(rpos.map(|e| e as f32))
247                            .map(|e| e.floor() as i32);
248
249                        let tower0_dist = tower0
250                            .offset
251                            .map(|e| e as f32)
252                            .distance(projected.map(|e| e as f32));
253                        let tower1_dist = tower1
254                            .offset
255                            .map(|e| e as f32)
256                            .distance(projected.map(|e| e as f32));
257                        let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
258                        let wall_ori = if (tower0.offset.x - tower1.offset.x).abs()
259                            < (tower0.offset.y - tower1.offset.y).abs()
260                        {
261                            Ori::North
262                        } else {
263                            Ori::East
264                        };
265
266                        (
267                            wall.distance_to_point(rpos.map(|e| e as f32)) as i32,
268                            projected,
269                            Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32,
270                            wall_ori,
271                            [tower0, tower1],
272                        )
273                    })
274                    .min_by_key(|x| x.0)
275                    .unwrap();
276                let border_pos = (wall_pos - rpos).map(|e| e.abs());
277                let wall_rpos = if wall_ori == Ori::East {
278                    rpos
279                } else {
280                    rpos.yx()
281                };
282                let head_space = col_sample
283                    .path
284                    .map(|(dist, _, path, _)| path.head_space(dist))
285                    .unwrap_or(0);
286
287                let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) {
288                    col
289                } else {
290                    col_sample
291                };
292
293                // Make sure particularly weird terrain doesn't give us underground walls
294                let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0);
295
296                let keep_archetype = KeepArchetype {
297                    flag_color: if self.evil {
298                        FlagColor::Evil
299                    } else {
300                        FlagColor::Good
301                    },
302                    stone_color: if self.evil {
303                        StoneColor::Evil
304                    } else {
305                        StoneColor::Good
306                    },
307                };
308
309                for z in -10..64 {
310                    let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
311
312                    // Boundary wall
313                    let wall_z = wpos.z - wall_alt;
314                    if z < head_space {
315                        continue;
316                    }
317
318                    let mut mask = keep_archetype.draw(
319                        index,
320                        Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
321                        wall_dist,
322                        border_pos,
323                        rpos - wall_pos,
324                        wall_z,
325                        wall_ori,
326                        4,
327                        0,
328                        &Attr {
329                            storeys: 2,
330                            is_tower: false,
331                            flag: self.flags,
332                            ridged: false,
333                            rounded: true,
334                            has_doors: false,
335                        },
336                    );
337
338                    // Boundary towers
339                    for tower in &self.towers {
340                        let tower_wpos = Vec3::new(
341                            self.origin.x + tower.offset.x,
342                            self.origin.y + tower.offset.y,
343                            tower.alt,
344                        );
345                        let tower_locus = 10;
346
347                        let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
348                        mask = mask.resolve_with(keep_archetype.draw(
349                            index,
350                            if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
351                                wpos - tower_wpos
352                            } else {
353                                Vec3::new(
354                                    wpos.y - tower_wpos.y,
355                                    wpos.x - tower_wpos.x,
356                                    wpos.z - tower_wpos.z,
357                                )
358                            },
359                            border_pos.reduce_max() - tower_locus,
360                            Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
361                            (wpos - tower_wpos).xy(),
362                            wpos.z - tower.alt,
363                            if border_pos.x > border_pos.y {
364                                Ori::East
365                            } else {
366                                Ori::North
367                            },
368                            tower_locus,
369                            0,
370                            &Attr {
371                                storeys: 3,
372                                is_tower: true,
373                                flag: self.flags,
374                                ridged: self.ridged,
375                                rounded: self.rounded_towers,
376                                has_doors: false,
377                            },
378                        ));
379                    }
380
381                    // Keeps
382                    for keep in &self.keeps {
383                        let keep_wpos = Vec3::new(
384                            self.origin.x + keep.offset.x,
385                            self.origin.y + keep.offset.y,
386                            keep.alt,
387                        );
388
389                        let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
390                        mask = mask.resolve_with(keep_archetype.draw(
391                            index,
392                            if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
393                                wpos - keep_wpos
394                            } else {
395                                Vec3::new(
396                                    wpos.y - keep_wpos.y,
397                                    wpos.x - keep_wpos.x,
398                                    wpos.z - keep_wpos.z,
399                                )
400                            },
401                            border_pos.reduce_max() - keep.locus,
402                            Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
403                            (wpos - keep_wpos).xy(),
404                            wpos.z - keep.alt,
405                            if border_pos.x > border_pos.y {
406                                Ori::East
407                            } else {
408                                Ori::North
409                            },
410                            keep.locus,
411                            0,
412                            &Attr {
413                                storeys: keep.storeys,
414                                is_tower: keep.is_tower,
415                                flag: self.flags,
416                                ridged: self.ridged,
417                                rounded: self.rounded_towers,
418                                has_doors: true,
419                            },
420                        ));
421                    }
422
423                    if let Some(block) = mask.finish() {
424                        let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block);
425                    }
426                }
427            }
428        }
429    }
430
431    pub fn apply_supplement<'a>(
432        &'a self,
433        // NOTE: Used only for dynamic elements like chests and entities!
434        _dynamic_rng: &mut impl Rng,
435        _wpos2d: Vec2<i32>,
436        _get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
437        _supplement: &mut ChunkSupplement,
438    ) {
439        // TODO
440    }
441}