Skip to main content

veloren_world/site/plot/
savannah_airship_dock.rs

1use super::*;
2use crate::{
3    Land,
4    site::generation::{place_circular_as_vec, spiral_staircase},
5    util::{CARDINALS, DIAGONALS, RandomField, Sampler, within_distance},
6};
7use common::{
8    generation::SpecialEntity,
9    terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::{f32::consts::TAU, sync::Arc};
13use vek::*;
14
15/// Represents house data generated by the `generate()` method
16pub struct SavannahAirshipDock {
17    /// Tile position of the door tile
18    pub door_tile: Vec2<i32>,
19    /// Approximate altitude of the door tile
20    pub(crate) alt: i32,
21    pub center: Vec2<i32>,
22    length: i32,
23    platform_height: i32,
24    pub docking_positions: Vec<Vec3<i32>>,
25}
26
27impl SavannahAirshipDock {
28    pub fn generate(
29        land: &Land,
30        _rng: &mut impl Rng,
31        site: &Site,
32        door_tile: Vec2<i32>,
33        tile_aabr: Aabr<i32>,
34    ) -> Self {
35        let door_tile_pos = site.tile_center_wpos(door_tile);
36        let bounds = Aabr {
37            min: site.tile_wpos(tile_aabr.min),
38            max: site.tile_wpos(tile_aabr.max),
39        };
40        let center = bounds.center();
41        let length = 21;
42
43        // dock is 5 tiles = 30 blocks in radius
44        // airships are 37 blocks wide = 6 tiles.
45        // distance from the center to the outside edge of the airship when docked is 11
46        // tiles. The area covered by all four airships is a square 22 tiles on
47        // a side.
48
49        // Sample the surface altitude every 4 tiles (= 24 blocks) around the dock
50        // center to find the highest point of terrain surrounding the dock.
51        let mut max_surface_alt = i32::MIN;
52        // -12 -8 -4 0 +4 +8 +12
53        for dx in -3..=3 {
54            for dy in -3..=3 {
55                let pos = center + Vec2::new(dx * 24, dy * 24);
56                let alt = land.get_surface_alt_approx(pos) as i32;
57                if alt > max_surface_alt {
58                    max_surface_alt = alt;
59                }
60            }
61        }
62
63        // The foundation has a radius of length + 4 blocks.
64        // Sample the surface altitude over the foundation area to get the
65        // foundation min alt.
66        let foundation_qtr = (length + 4) / 2;
67        let mut min_foundation_alt = i32::MAX;
68        for dx in -2..=2 {
69            for dy in -2..=2 {
70                let pos = center + Vec2::new(dx * foundation_qtr, dy * foundation_qtr);
71                let alt = land.get_surface_alt_approx(pos) as i32;
72                if alt < min_foundation_alt {
73                    min_foundation_alt = alt;
74                }
75            }
76        }
77
78        let mut base_alt = min_foundation_alt + 2;
79        let mut platform_height = 40;
80        let mut top_floor = base_alt + 1 + platform_height - 3;
81        let clearance = top_floor - (max_surface_alt + 5);
82        if clearance < 0 {
83            base_alt -= clearance;
84            platform_height -= clearance;
85            top_floor -= clearance;
86        }
87
88        let docking_positions = CARDINALS
89            .iter()
90            .map(|dir| (center + dir * 31).with_z(top_floor))
91            .collect::<Vec<_>>();
92        Self {
93            door_tile: door_tile_pos,
94            alt: base_alt,
95            center,
96            length,
97            platform_height,
98            docking_positions,
99        }
100    }
101}
102
103impl Structure for SavannahAirshipDock {
104    #[cfg(feature = "dyn-lib")]
105    #[unsafe(export_name = "as_dyn_structure_savannahairshipdock")]
106    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
107        Some((
108            Self::as_dyn_impl(self),
109            "as_dyn_structure_savannahairshipdock",
110        ))
111    }
112
113    fn spawn_rules_inner(
114        &self,
115        spawn_rules: &mut SpawnRules,
116        _land: &Land,
117        wpos: Vec2<i32>,
118        _weight: f32,
119    ) {
120        // dock is 5 tiles = 30 blocks in radius
121        // airships are 39 blocks wide.
122        // Tree can be up to 20 blocks in radius.
123        // Don't allow trees within 30 + 39 + 20 = 89 blocks of the dock center
124        const AIRSHIP_MIN_TREE_DIST: i32 = 89;
125        spawn_rules.trees &= !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST);
126        spawn_rules.waypoints = false;
127    }
128
129    fn airship_dock_info(&self) -> Option<AirshipDockInfo<'_>> {
130        Some(AirshipDockInfo {
131            door_tile: self.door_tile,
132            center: self.center,
133            docking_positions: &self.docking_positions,
134        })
135    }
136
137    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
138        let base = self.alt + 1;
139        let center = self.center;
140        let wood_dark = Fill::Brick(BlockKind::Misc, Rgb::new(142, 67, 27), 12);
141        let reed = Fill::Brick(BlockKind::Misc, Rgb::new(72, 55, 46), 22);
142        let clay = Fill::Brick(BlockKind::Misc, Rgb::new(209, 124, 57), 22);
143        let color = Fill::Sampling(Arc::new(|center| {
144            Some(match (RandomField::new(0).get(center)) % 7 {
145                0 => Block::new(BlockKind::GlowingRock, Rgb::new(153, 82, 40)),
146                1 => Block::new(BlockKind::GlowingRock, Rgb::new(172, 104, 57)),
147                2 => Block::new(BlockKind::GlowingRock, Rgb::new(135, 106, 100)),
148                3 => Block::new(BlockKind::GlowingRock, Rgb::new(198, 164, 139)),
149                4 => Block::new(BlockKind::GlowingRock, Rgb::new(168, 163, 157)),
150                5 => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)),
151                _ => Block::new(BlockKind::GlowingRock, Rgb::new(178, 124, 90)),
152            })
153        }));
154        let length = self.length;
155        let height = length / 2;
156        let platform_height = self.platform_height;
157        let storeys = 1;
158        let radius = length + (length / 3);
159        let reed_var = (1 + RandomField::new(0).get(center.with_z(base)) % 4) as f32;
160        let reed_parts = 36_f32 + reed_var;
161        let phi = TAU / reed_parts;
162
163        // foundation
164        painter
165            .cylinder(Aabb {
166                min: (center - length).with_z(base - 3),
167                max: (center + length + 1).with_z(base - 2),
168            })
169            .fill(clay.clone());
170        painter
171            .cylinder(Aabb {
172                min: (center - length - 1).with_z(base - 4),
173                max: (center + length + 2).with_z(base - 3),
174            })
175            .fill(clay.clone());
176        painter
177            .cylinder(Aabb {
178                min: (center - length - 2).with_z(base - 5),
179                max: (center + length + 3).with_z(base - 4),
180            })
181            .fill(clay.clone());
182        painter
183            .cylinder(Aabb {
184                min: (center - length - 3).with_z(base - height),
185                max: (center + length + 4).with_z(base - 5),
186            })
187            .fill(clay.clone());
188        // platform
189        painter
190            .cylinder(Aabb {
191                min: (center - (2 * (length / 3)) - 1).with_z(base + platform_height - 4),
192                max: (center + (2 * (length / 3)) + 1).with_z(base + platform_height - 3),
193            })
194            .fill(color.clone());
195        painter
196            .cylinder(Aabb {
197                min: (center - (2 * (length / 3))).with_z(base + platform_height - 4),
198                max: (center + (2 * (length / 3))).with_z(base + platform_height - 3),
199            })
200            .fill(clay.clone());
201        painter
202            .cylinder(Aabb {
203                min: (center - length - 2).with_z(base + platform_height - 3),
204                max: (center + length + 2).with_z(base + platform_height - 2),
205            })
206            .fill(color.clone());
207        painter
208            .cylinder(Aabb {
209                min: (center - length - 1).with_z(base + platform_height - 3),
210                max: (center + length + 1).with_z(base + platform_height - 2),
211            })
212            .fill(clay.clone());
213        // docks
214        for dir in CARDINALS {
215            let dock_pos = center + dir * 26;
216            painter
217                .cylinder(Aabb {
218                    min: (dock_pos - 5).with_z(base + platform_height - 3),
219                    max: (dock_pos + 5).with_z(base + platform_height - 2),
220                })
221                .fill(color.clone());
222            painter
223                .cylinder(Aabb {
224                    min: (dock_pos - 4).with_z(base + platform_height - 3),
225                    max: (dock_pos + 4).with_z(base + platform_height - 2),
226                })
227                .fill(wood_dark.clone());
228        }
229
230        // lanterns, crates & barrels
231        for dir in CARDINALS {
232            let lantern_pos = center + (dir * length);
233
234            painter.sprite(
235                lantern_pos.with_z(base + platform_height - 2),
236                SpriteKind::Lantern,
237            );
238        }
239        for dir in DIAGONALS {
240            let cargo_pos = center + (dir * ((length / 2) - 1));
241            for dir in CARDINALS {
242                let sprite_pos = cargo_pos + dir;
243                let rows = (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
244                for r in 0..rows {
245                    painter
246                        .aabb(Aabb {
247                            min: (sprite_pos).with_z(base + platform_height - 2 + r),
248                            max: (sprite_pos + 1).with_z(base + platform_height - 1 + r),
249                        })
250                        .fill(Fill::Block(Block::air(
251                            match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2) as i32
252                            {
253                                0 => SpriteKind::Barrel,
254                                _ => SpriteKind::CrateBlock,
255                            },
256                        )));
257                    if r > 0 {
258                        painter.owned_resource_sprite(
259                            sprite_pos.with_z(base + platform_height - 1 + r),
260                            SpriteKind::Crate,
261                            0,
262                        );
263                    }
264                }
265            }
266        }
267        // campfire
268        let campfire_pos = (center - (2 * (length / 3)) - 1).with_z(base + platform_height);
269        painter.spawn(
270            EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
271                .into_special(SpecialEntity::Waypoint),
272        );
273        for b in 0..2 {
274            let base = base + (b * platform_height);
275            let radius = radius - (b * (radius / 3));
276            let length = length - (b * (length / 3));
277            // roof cone
278            painter
279                .cone(Aabb {
280                    min: (center - radius).with_z(base + (storeys * height) - (height / 2) + 1),
281                    max: (center + radius)
282                        .with_z(base + (storeys * height) + (height / 2) - 1 + reed_var as i32),
283                })
284                .fill(reed.clone());
285            painter
286                .cone(Aabb {
287                    min: (center - radius).with_z(base + (storeys * height) - (height / 2)),
288                    max: (center + radius)
289                        .with_z(base + (storeys * height) + (height / 2) - 2 + reed_var as i32),
290                })
291                .clear();
292
293            if b == 1 {
294                // Agent Desk
295                let agent_z_pos = base - 2 + ((storeys - 1) * (height + 2));
296                painter
297                    .cylinder_with_radius(
298                        center.with_z(agent_z_pos),
299                        (length + 1 - (storeys - 1)) as f32,
300                        6.0,
301                    )
302                    .intersect(painter.aabb(Aabb {
303                        min: (Vec2::new(center.x - 3, center.y + 4)).with_z(agent_z_pos),
304                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 7),
305                    }))
306                    .fill(clay.clone());
307                painter
308                    .cylinder_with_radius(
309                        center.with_z(agent_z_pos),
310                        (length + 1 - (storeys - 1)) as f32,
311                        5.0,
312                    )
313                    .intersect(painter.aabb(Aabb {
314                        min: (Vec2::new(center.x - 4, center.y + 5)).with_z(agent_z_pos),
315                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 6),
316                    }))
317                    .clear();
318                painter
319                    .cylinder_with_radius(
320                        Vec2::new(center.x - 4, center.y + 5).with_z(agent_z_pos),
321                        (length - 4 - (storeys - 1)) as f32,
322                        1.0,
323                    )
324                    .intersect(painter.aabb(Aabb {
325                        min: (Vec2::new(center.x - 4, center.y + 5)).with_z(agent_z_pos),
326                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 2),
327                    }))
328                    .fill(color.clone());
329                painter
330                    .cylinder_with_radius(
331                        Vec2::new(center.x - 4, center.y + 5).with_z(agent_z_pos),
332                        (length - 5 - (storeys - 1)) as f32,
333                        1.0,
334                    )
335                    .intersect(painter.aabb(Aabb {
336                        min: (Vec2::new(center.x - 4, center.y + 5)).with_z(agent_z_pos),
337                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 2),
338                    }))
339                    .clear();
340            }
341            // room
342            for s in 0..storeys {
343                let room = painter.cylinder(Aabb {
344                    min: (center - length + 2 + s).with_z(base - 2 + (s * height)),
345                    max: (center + 1 + length - 2 - s).with_z(base + height + (s * height)),
346                });
347                room.fill(clay.clone());
348                // decor inlays
349                for dir in DIAGONALS {
350                    let decor_pos = center + dir * (length - 2 - s);
351                    let decor = painter
352                        .line(
353                            center.with_z(base - 1 + (s * (height + 2))),
354                            decor_pos.with_z(base - 1 + (s * (height + 2))),
355                            5.0,
356                        )
357                        .intersect(room);
358                    decor.fill(color.clone());
359                    painter
360                        .line(
361                            center.with_z(base - 1 + (s * (height + 2))),
362                            decor_pos.with_z(base - 1 + (s * (height + 2))),
363                            4.0,
364                        )
365                        .intersect(decor)
366                        .fill(clay.clone());
367                }
368            }
369
370            // clear rooms
371            painter
372                .cylinder(Aabb {
373                    min: (center - length + 4).with_z(base - 2),
374                    max: (center + 1 + length - 4).with_z(base + (storeys * height)),
375                })
376                .clear();
377            // wood decor
378            painter
379                .cylinder(Aabb {
380                    min: (center - length + 4).with_z(base - 1),
381                    max: (center + 1 + length - 4).with_z(base),
382                })
383                .fill(wood_dark.clone());
384            painter
385                .cylinder(Aabb {
386                    min: (center - length + 4).with_z(base + (storeys * height) - 1),
387                    max: (center + 1 + length - 4).with_z(base + (storeys * height) + 1),
388                })
389                .fill(wood_dark.clone());
390
391            for s in 0..storeys {
392                // entries, windows
393                for dir in CARDINALS {
394                    let frame_pos = center + dir * (length - 2 - s);
395                    let clear_pos = center + dir * (length + 2 - s);
396
397                    painter
398                        .line(
399                            center.with_z(base - 1 + (s * (height + 2))),
400                            frame_pos.with_z(base - 1 + (s * (height + 2))),
401                            3.0,
402                        )
403                        .fill(color.clone());
404                    painter
405                        .line(
406                            center.with_z(base - 1 + (s * (height + 2))),
407                            clear_pos.with_z(base - 1 + (s * (height + 2))),
408                            2.0,
409                        )
410                        .clear();
411                }
412            }
413            // re clear room
414            painter
415                .cylinder(Aabb {
416                    min: (center - length + 5).with_z(base - 2),
417                    max: (center + 1 + length - 5).with_z(base + (storeys * height) + 1),
418                })
419                .clear();
420            // floor
421            painter
422                .cylinder(Aabb {
423                    min: (center - (length / 2) - 1).with_z(base - 3),
424                    max: (center + (length / 2) + 1).with_z(base - 2),
425                })
426                .fill(color.clone());
427            painter
428                .cylinder(Aabb {
429                    min: (center - (length / 2) + 1).with_z(base - 3),
430                    max: (center + (length / 2) - 1).with_z(base - 2),
431                })
432                .fill(clay.clone());
433
434            // reed roof lines
435            for n in 1..=reed_parts as i32 {
436                let pos = Vec2::new(
437                    center.x + ((radius as f32) * ((n as f32 * phi).cos())) as i32,
438                    center.y + ((radius as f32) * ((n as f32 * phi).sin())) as i32,
439                );
440                painter
441                    .line(
442                        pos.with_z(base + (storeys * height) - (height / 2)),
443                        center.with_z(base + (storeys * height) + (height / 2) + reed_var as i32),
444                        1.0,
445                    )
446                    .fill(reed.clone());
447            }
448        }
449
450        // tower
451        let beams_low = place_circular_as_vec(center, (2 * (length / 3)) as f32, 10);
452        let beams_high = place_circular_as_vec(center, (2 * (length / 4)) as f32, 10);
453
454        for b in 0..beams_low.len() {
455            painter
456                .cylinder(Aabb {
457                    min: (beams_low[b] - 4).with_z(base + height - 1),
458                    max: (beams_low[b] + 4).with_z(base + height),
459                })
460                .fill(wood_dark.clone());
461
462            painter
463                .line(
464                    beams_low[b].with_z(base + height),
465                    beams_high[b].with_z(base + platform_height - 4),
466                    1.5,
467                )
468                .fill(wood_dark.clone());
469        }
470        //stairs
471        painter
472            .cylinder(Aabb {
473                min: (center - (length / 3)).with_z(base),
474                max: (center + (length / 3)).with_z(base + platform_height),
475            })
476            .clear();
477
478        let stairs = painter.cylinder(Aabb {
479            min: (center - (length / 3)).with_z(base - 3),
480            max: (center + (length / 3)).with_z(base + platform_height - 2),
481        });
482
483        stairs
484            .sample(spiral_staircase(
485                center.with_z(base - 3),
486                ((length / 3) + 1) as f32,
487                0.5,
488                (platform_height / 4) as f32,
489            ))
490            .fill(clay.clone());
491    }
492}