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    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
103        SpawnRules {
104            trees: {
105                // dock is 5 tiles = 30 blocks in radius
106                // airships are 39 blocks wide.
107                // Tree can be up to 20 blocks in radius.
108                // Don't allow trees within 30 + 39 + 20 = 89 blocks of the dock center
109                const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
110                !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
111            },
112            waypoints: false,
113            ..SpawnRules::default()
114        }
115    }
116}
117
118impl Structure for SavannahAirshipDock {
119    #[cfg(feature = "use-dyn-lib")]
120    const UPDATE_FN: &'static [u8] = b"render_savannah_airship_dock\0";
121
122    #[cfg_attr(
123        feature = "be-dyn-lib",
124        unsafe(export_name = "render_savannah_airship_dock")
125    )]
126    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
127        let base = self.alt + 1;
128        let center = self.center;
129        let wood_dark = Fill::Brick(BlockKind::Misc, Rgb::new(142, 67, 27), 12);
130        let reed = Fill::Brick(BlockKind::Misc, Rgb::new(72, 55, 46), 22);
131        let clay = Fill::Brick(BlockKind::Misc, Rgb::new(209, 124, 57), 22);
132        let color = Fill::Sampling(Arc::new(|center| {
133            Some(match (RandomField::new(0).get(center)) % 7 {
134                0 => Block::new(BlockKind::GlowingRock, Rgb::new(153, 82, 40)),
135                1 => Block::new(BlockKind::GlowingRock, Rgb::new(172, 104, 57)),
136                2 => Block::new(BlockKind::GlowingRock, Rgb::new(135, 106, 100)),
137                3 => Block::new(BlockKind::GlowingRock, Rgb::new(198, 164, 139)),
138                4 => Block::new(BlockKind::GlowingRock, Rgb::new(168, 163, 157)),
139                5 => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)),
140                _ => Block::new(BlockKind::GlowingRock, Rgb::new(178, 124, 90)),
141            })
142        }));
143        let length = self.length;
144        let height = length / 2;
145        let platform_height = self.platform_height;
146        let storeys = 1;
147        let radius = length + (length / 3);
148        let reed_var = (1 + RandomField::new(0).get(center.with_z(base)) % 4) as f32;
149        let reed_parts = 36_f32 + reed_var;
150        let phi = TAU / reed_parts;
151
152        // foundation
153        painter
154            .cylinder(Aabb {
155                min: (center - length).with_z(base - 3),
156                max: (center + length + 1).with_z(base - 2),
157            })
158            .fill(clay.clone());
159        painter
160            .cylinder(Aabb {
161                min: (center - length - 1).with_z(base - 4),
162                max: (center + length + 2).with_z(base - 3),
163            })
164            .fill(clay.clone());
165        painter
166            .cylinder(Aabb {
167                min: (center - length - 2).with_z(base - 5),
168                max: (center + length + 3).with_z(base - 4),
169            })
170            .fill(clay.clone());
171        painter
172            .cylinder(Aabb {
173                min: (center - length - 3).with_z(base - height),
174                max: (center + length + 4).with_z(base - 5),
175            })
176            .fill(clay.clone());
177        // platform
178        painter
179            .cylinder(Aabb {
180                min: (center - (2 * (length / 3)) - 1).with_z(base + platform_height - 4),
181                max: (center + (2 * (length / 3)) + 1).with_z(base + platform_height - 3),
182            })
183            .fill(color.clone());
184        painter
185            .cylinder(Aabb {
186                min: (center - (2 * (length / 3))).with_z(base + platform_height - 4),
187                max: (center + (2 * (length / 3))).with_z(base + platform_height - 3),
188            })
189            .fill(clay.clone());
190        painter
191            .cylinder(Aabb {
192                min: (center - length - 2).with_z(base + platform_height - 3),
193                max: (center + length + 2).with_z(base + platform_height - 2),
194            })
195            .fill(color.clone());
196        painter
197            .cylinder(Aabb {
198                min: (center - length - 1).with_z(base + platform_height - 3),
199                max: (center + length + 1).with_z(base + platform_height - 2),
200            })
201            .fill(clay.clone());
202        // docks
203        for dir in CARDINALS {
204            let dock_pos = center + dir * 26;
205            painter
206                .cylinder(Aabb {
207                    min: (dock_pos - 5).with_z(base + platform_height - 3),
208                    max: (dock_pos + 5).with_z(base + platform_height - 2),
209                })
210                .fill(color.clone());
211            painter
212                .cylinder(Aabb {
213                    min: (dock_pos - 4).with_z(base + platform_height - 3),
214                    max: (dock_pos + 4).with_z(base + platform_height - 2),
215                })
216                .fill(wood_dark.clone());
217        }
218
219        // lanterns, crates & barrels
220        for dir in CARDINALS {
221            let lantern_pos = center + (dir * length);
222
223            painter.sprite(
224                lantern_pos.with_z(base + platform_height - 2),
225                SpriteKind::Lantern,
226            );
227        }
228        for dir in DIAGONALS {
229            let cargo_pos = center + (dir * ((length / 2) - 1));
230            for dir in CARDINALS {
231                let sprite_pos = cargo_pos + dir;
232                let rows = (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
233                for r in 0..rows {
234                    painter
235                        .aabb(Aabb {
236                            min: (sprite_pos).with_z(base + platform_height - 2 + r),
237                            max: (sprite_pos + 1).with_z(base + platform_height - 1 + r),
238                        })
239                        .fill(Fill::Block(Block::air(
240                            match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2) as i32
241                            {
242                                0 => SpriteKind::Barrel,
243                                _ => SpriteKind::CrateBlock,
244                            },
245                        )));
246                    if r > 0 {
247                        painter.owned_resource_sprite(
248                            sprite_pos.with_z(base + platform_height - 1 + r),
249                            SpriteKind::Crate,
250                            0,
251                        );
252                    }
253                }
254            }
255        }
256        // campfire
257        let campfire_pos = (center - (2 * (length / 3)) - 1).with_z(base + platform_height);
258        painter.spawn(
259            EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
260                .into_special(SpecialEntity::Waypoint),
261        );
262        for b in 0..2 {
263            let base = base + (b * platform_height);
264            let radius = radius - (b * (radius / 3));
265            let length = length - (b * (length / 3));
266            // roof cone
267            painter
268                .cone(Aabb {
269                    min: (center - radius).with_z(base + (storeys * height) - (height / 2) + 1),
270                    max: (center + radius)
271                        .with_z(base + (storeys * height) + (height / 2) - 1 + reed_var as i32),
272                })
273                .fill(reed.clone());
274            painter
275                .cone(Aabb {
276                    min: (center - radius).with_z(base + (storeys * height) - (height / 2)),
277                    max: (center + radius)
278                        .with_z(base + (storeys * height) + (height / 2) - 2 + reed_var as i32),
279                })
280                .clear();
281
282            if b == 1 {
283                // Agent Desk
284                let agent_z_pos = base - 2 + ((storeys - 1) * (height + 2));
285                painter
286                    .cylinder_with_radius(
287                        center.with_z(agent_z_pos),
288                        (length + 1 - (storeys - 1)) as f32,
289                        6.0,
290                    )
291                    .intersect(painter.aabb(Aabb {
292                        min: (Vec2::new(center.x - 3, center.y + 4)).with_z(agent_z_pos),
293                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 7),
294                    }))
295                    .fill(clay.clone());
296                painter
297                    .cylinder_with_radius(
298                        center.with_z(agent_z_pos),
299                        (length + 1 - (storeys - 1)) as f32,
300                        5.0,
301                    )
302                    .intersect(painter.aabb(Aabb {
303                        min: (Vec2::new(center.x - 4, center.y + 5)).with_z(agent_z_pos),
304                        max: (Vec2::new(center.x - 30, center.y + 30)).with_z(agent_z_pos + 6),
305                    }))
306                    .clear();
307                painter
308                    .cylinder_with_radius(
309                        Vec2::new(center.x - 4, center.y + 5).with_z(agent_z_pos),
310                        (length - 4 - (storeys - 1)) as f32,
311                        1.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 + 2),
316                    }))
317                    .fill(color.clone());
318                painter
319                    .cylinder_with_radius(
320                        Vec2::new(center.x - 4, center.y + 5).with_z(agent_z_pos),
321                        (length - 5 - (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                    .clear();
329            }
330            // room
331            for s in 0..storeys {
332                let room = painter.cylinder(Aabb {
333                    min: (center - length + 2 + s).with_z(base - 2 + (s * height)),
334                    max: (center + 1 + length - 2 - s).with_z(base + height + (s * height)),
335                });
336                room.fill(clay.clone());
337                // decor inlays
338                for dir in DIAGONALS {
339                    let decor_pos = center + dir * (length - 2 - s);
340                    let decor = painter
341                        .line(
342                            center.with_z(base - 1 + (s * (height + 2))),
343                            decor_pos.with_z(base - 1 + (s * (height + 2))),
344                            5.0,
345                        )
346                        .intersect(room);
347                    decor.fill(color.clone());
348                    painter
349                        .line(
350                            center.with_z(base - 1 + (s * (height + 2))),
351                            decor_pos.with_z(base - 1 + (s * (height + 2))),
352                            4.0,
353                        )
354                        .intersect(decor)
355                        .fill(clay.clone());
356                }
357            }
358
359            // clear rooms
360            painter
361                .cylinder(Aabb {
362                    min: (center - length + 4).with_z(base - 2),
363                    max: (center + 1 + length - 4).with_z(base + (storeys * height)),
364                })
365                .clear();
366            // wood decor
367            painter
368                .cylinder(Aabb {
369                    min: (center - length + 4).with_z(base - 1),
370                    max: (center + 1 + length - 4).with_z(base),
371                })
372                .fill(wood_dark.clone());
373            painter
374                .cylinder(Aabb {
375                    min: (center - length + 4).with_z(base + (storeys * height) - 1),
376                    max: (center + 1 + length - 4).with_z(base + (storeys * height) + 1),
377                })
378                .fill(wood_dark.clone());
379
380            for s in 0..storeys {
381                // entries, windows
382                for dir in CARDINALS {
383                    let frame_pos = center + dir * (length - 2 - s);
384                    let clear_pos = center + dir * (length + 2 - s);
385
386                    painter
387                        .line(
388                            center.with_z(base - 1 + (s * (height + 2))),
389                            frame_pos.with_z(base - 1 + (s * (height + 2))),
390                            3.0,
391                        )
392                        .fill(color.clone());
393                    painter
394                        .line(
395                            center.with_z(base - 1 + (s * (height + 2))),
396                            clear_pos.with_z(base - 1 + (s * (height + 2))),
397                            2.0,
398                        )
399                        .clear();
400                }
401            }
402            // re clear room
403            painter
404                .cylinder(Aabb {
405                    min: (center - length + 5).with_z(base - 2),
406                    max: (center + 1 + length - 5).with_z(base + (storeys * height) + 1),
407                })
408                .clear();
409            // floor
410            painter
411                .cylinder(Aabb {
412                    min: (center - (length / 2) - 1).with_z(base - 3),
413                    max: (center + (length / 2) + 1).with_z(base - 2),
414                })
415                .fill(color.clone());
416            painter
417                .cylinder(Aabb {
418                    min: (center - (length / 2) + 1).with_z(base - 3),
419                    max: (center + (length / 2) - 1).with_z(base - 2),
420                })
421                .fill(clay.clone());
422
423            // reed roof lines
424            for n in 1..=reed_parts as i32 {
425                let pos = Vec2::new(
426                    center.x + ((radius as f32) * ((n as f32 * phi).cos())) as i32,
427                    center.y + ((radius as f32) * ((n as f32 * phi).sin())) as i32,
428                );
429                painter
430                    .line(
431                        pos.with_z(base + (storeys * height) - (height / 2)),
432                        center.with_z(base + (storeys * height) + (height / 2) + reed_var as i32),
433                        1.0,
434                    )
435                    .fill(reed.clone());
436            }
437        }
438
439        // tower
440        let beams_low = place_circular_as_vec(center, (2 * (length / 3)) as f32, 10);
441        let beams_high = place_circular_as_vec(center, (2 * (length / 4)) as f32, 10);
442
443        for b in 0..beams_low.len() {
444            painter
445                .cylinder(Aabb {
446                    min: (beams_low[b] - 4).with_z(base + height - 1),
447                    max: (beams_low[b] + 4).with_z(base + height),
448                })
449                .fill(wood_dark.clone());
450
451            painter
452                .line(
453                    beams_low[b].with_z(base + height),
454                    beams_high[b].with_z(base + platform_height - 4),
455                    1.5,
456                )
457                .fill(wood_dark.clone());
458        }
459        //stairs
460        painter
461            .cylinder(Aabb {
462                min: (center - (length / 3)).with_z(base),
463                max: (center + (length / 3)).with_z(base + platform_height),
464            })
465            .clear();
466
467        let stairs = painter.cylinder(Aabb {
468            min: (center - (length / 3)).with_z(base - 3),
469            max: (center + (length / 3)).with_z(base + platform_height - 2),
470        });
471
472        stairs
473            .sample(spiral_staircase(
474                center.with_z(base - 3),
475                ((length / 3) + 1) as f32,
476                0.5,
477                (platform_height / 4) as f32,
478            ))
479            .fill(clay.clone());
480    }
481}