veloren_world/site/plot/
coastal_airship_dock.rs

1use super::*;
2use crate::{
3    Land,
4    site::generation::{PrimitiveTransform, place_circular},
5    util::{CARDINALS, RandomField, Sampler, within_distance},
6};
7use common::{
8    generation::SpecialEntity,
9    terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::sync::Arc;
13use vek::*;
14
15/// Represents house data generated by the `generate()` method
16pub struct CoastalAirshipDock {
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    base: i32,
22    pub center: Vec2<i32>,
23    size: i32,
24    bldg_height: i32,
25    diameter: i32,
26    pub docking_positions: Vec<Vec3<i32>>,
27}
28
29impl CoastalAirshipDock {
30    pub fn generate(
31        land: &Land,
32        _rng: &mut impl Rng,
33        site: &Site,
34        door_tile: Vec2<i32>,
35        tile_aabr: Aabr<i32>,
36    ) -> Self {
37        let door_tile_pos = site.tile_center_wpos(door_tile);
38        let bounds = Aabr {
39            min: site.tile_wpos(tile_aabr.min),
40            max: site.tile_wpos(tile_aabr.max),
41        };
42        let diameter = (bounds.max.x - bounds.min.x).min(bounds.max.y - bounds.min.y);
43        let center = bounds.center();
44
45        // dock is 5 tiles = 30 blocks in radius
46        // airships are 37 blocks wide = 6 tiles.
47        // distance from the center to the outside edge of the airship when docked is 11
48        // tiles. The area covered by all four airships is a square 22 tiles on
49        // a side.
50
51        // Sample the surface altitude every 4 tiles (= 24 blocks) around the dock
52        // center to find the highest point of terrain surrounding the dock.
53        let mut max_surface_alt = i32::MIN;
54        // -12 -8 -4 0 +4 +8 +12
55        for dx in -3..=3 {
56            for dy in -3..=3 {
57                let pos = center + Vec2::new(dx * 24, dy * 24);
58                let alt = land.get_surface_alt_approx(pos) as i32;
59                if alt > max_surface_alt {
60                    max_surface_alt = alt;
61                }
62            }
63        }
64
65        // The foundation is radius - 6 blocks.
66        // Sample the surface altitude over the foundation area to get the
67        // foundation min alt.
68        let foundation_qtr = diameter / 4;
69        let mut min_foundation_alt = i32::MAX;
70        for dx in -2..=2 {
71            for dy in -2..=2 {
72                let pos = center + Vec2::new(dx * foundation_qtr, dy * foundation_qtr);
73                let alt = land.get_surface_alt_approx(pos) as i32;
74                if alt < min_foundation_alt {
75                    min_foundation_alt = alt;
76                }
77            }
78        }
79
80        let mut alt = min_foundation_alt + 2;
81        let size = 20;
82        let bldg_height = 12;
83        let mut base = alt + 1;
84        let mut top_floor = base + (bldg_height * 6) - 3;
85
86        let clearance = top_floor - (max_surface_alt + 6);
87        if clearance < 0 {
88            alt += -clearance;
89            base += -clearance;
90            top_floor += -clearance
91        }
92
93        let docking_positions = CARDINALS
94            .iter()
95            .map(|dir| (center + dir * 31).with_z(top_floor - 1))
96            .collect::<Vec<_>>();
97        Self {
98            door_tile: door_tile_pos,
99            alt,
100            base,
101            center,
102            size,
103            bldg_height,
104            diameter,
105            docking_positions,
106        }
107    }
108
109    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
110        SpawnRules {
111            trees: {
112                // dock is 5 tiles = 30 blocks in radius
113                // airships are 39 blocks wide.
114                // Tree can be up to 20 blocks in radius.
115                // Don't allow trees within 30 + 39 + 20 = 89 blocks of the dock center
116                const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
117                !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
118            },
119            waypoints: false,
120            ..SpawnRules::default()
121        }
122    }
123}
124
125impl Structure for CoastalAirshipDock {
126    #[cfg(feature = "use-dyn-lib")]
127    const UPDATE_FN: &'static [u8] = b"render_coastal_airship_dock\0";
128
129    #[cfg_attr(
130        feature = "be-dyn-lib",
131        unsafe(export_name = "render_coastal_airship_dock")
132    )]
133    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
134        let base = self.base;
135        let center = self.center;
136        let white = Fill::Sampling(Arc::new(|center| {
137            Some(match (RandomField::new(0).get(center)) % 37 {
138                0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
139                9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
140                18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
141                27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
142                _ => Block::new(BlockKind::Rock, Rgb::new(255, 244, 193)),
143            })
144        }));
145        let blue_broken = Fill::Sampling(Arc::new(|center| {
146            Some(match (RandomField::new(0).get(center)) % 20 {
147                0 => Block::new(BlockKind::Rock, Rgb::new(30, 187, 235)),
148                _ => Block::new(BlockKind::Rock, Rgb::new(11, 146, 187)),
149            })
150        }));
151
152        let length = self.diameter / 2;
153        let width = (self.diameter / 2) - 1;
154        let height = 15;
155        // fence, blue gates
156        painter
157            .aabb(Aabb {
158                min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - 2),
159                max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 1),
160            })
161            .fill(blue_broken.clone());
162
163        for dir in CARDINALS {
164            let frame_pos = Vec2::new(
165                center.x + dir.x * (length + 5),
166                center.y + dir.y * (width + 5),
167            );
168            painter
169                .line(center.with_z(base - 1), frame_pos.with_z(base - 1), 3.0)
170                .fill(blue_broken.clone());
171        }
172        // foundation
173        painter
174            .aabb(Aabb {
175                min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - height),
176                max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 2),
177            })
178            .fill(white.clone());
179        for f in 0..8 {
180            painter
181                .aabb(Aabb {
182                    min: Vec2::new(center.x - length - 7 - f, center.y - width - 7 - f)
183                        .with_z(base - 3 - f),
184                    max: Vec2::new(center.x + length + 8 + f, center.y + width + 8 + f)
185                        .with_z(base - 2 - f),
186                })
187                .fill(white.clone());
188        }
189        // clear yard
190        painter
191            .aabb(Aabb {
192                min: Vec2::new(center.x - length - 5, center.y - width - 5).with_z(base - 2),
193                max: Vec2::new(center.x + length + 6, center.y + width + 6).with_z(base + height),
194            })
195            .clear();
196        // clear entries
197        for dir in CARDINALS {
198            let clear_pos = Vec2::new(
199                center.x + dir.x * (length + 7),
200                center.y + dir.y * (width + 7),
201            );
202            painter
203                .line(center.with_z(base - 1), clear_pos.with_z(base - 1), 2.0)
204                .clear();
205        }
206
207        // rooms
208        let size = self.size;
209        let room_offset = size / 6;
210        let bldg_height = self.bldg_height;
211        let tower_height = (bldg_height as f32 * 1.5).round() as i32;
212        for r in 0..=4 {
213            let bldg_size = size - (room_offset * r);
214            let bldg_base = base + ((bldg_height + 2) * r);
215            let level_height = bldg_base + bldg_height;
216            if r == 4 {
217                // Center platform
218                painter
219                    .cylinder_with_radius(
220                        center.with_z(level_height - 1),
221                        (bldg_size + 5) as f32,
222                        1.0,
223                    )
224                    .fill(white.clone());
225                // blue walls
226                painter
227                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 1.0)
228                    .fill(blue_broken.clone());
229
230                // Agent Desk
231
232                // Agent booth back wall, less than ~ 1 quadrant
233                let agent_booth_mask = painter.aabb(Aabb {
234                    min: (Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size - 4)))
235                        .with_z(level_height),
236                    max: (Vec2::new(center.x - (bldg_size + 5), center.y + (bldg_size + 5)))
237                        .with_z(level_height + 2),
238                });
239                painter
240                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 2.0)
241                    .intersect(agent_booth_mask)
242                    .fill(blue_broken.clone());
243
244                // Clear top
245                painter
246                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 4) as f32, 2.0)
247                    .clear();
248
249                // Agent booth front wall
250                painter
251                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 2) as f32, 1.0)
252                    .intersect(agent_booth_mask)
253                    .fill(blue_broken.clone());
254                // Clear excess Agent booth front wall
255                painter
256                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 1) as f32, 1.0)
257                    .intersect(agent_booth_mask)
258                    .clear();
259                // Agent booth connecting walls
260                painter
261                    .line(
262                        Vec2::new(center.x - (bldg_size + 3), center.y + (bldg_size - 5))
263                            .with_z(level_height),
264                        Vec2::new(center.x - (bldg_size + 2), center.y + (bldg_size - 5))
265                            .with_z(level_height),
266                        0.5,
267                    )
268                    .fill(blue_broken.clone());
269                painter
270                    .line(
271                        Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 1))
272                            .with_z(level_height),
273                        Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 2))
274                            .with_z(level_height),
275                        0.5,
276                    )
277                    .fill(blue_broken.clone());
278
279                // Clear the gangways
280                painter
281                    .aabb(Aabb {
282                        min: Vec2::new(center.x - 3, center.y + bldg_size * 2).with_z(level_height),
283                        max: Vec2::new(center.x + 3, center.y - (bldg_size * 2))
284                            .with_z(level_height + 1),
285                    })
286                    .clear();
287                painter
288                    .aabb(Aabb {
289                        min: Vec2::new(center.x - bldg_size * 2, center.y - 3).with_z(level_height),
290                        max: Vec2::new(center.x + bldg_size * 2, center.y + 3)
291                            .with_z(level_height + 1),
292                    })
293                    .clear();
294
295                // Cable Tower
296                painter
297                    .cylinder_with_radius(center.with_z(level_height), 1.0, tower_height as f32)
298                    .fill(white.clone());
299                painter
300                    .cone_with_radius(center.with_z(level_height + tower_height), 4.0, 3.0)
301                    .fill(white.clone());
302
303                let glowing =
304                    Fill::Block(Block::new(BlockKind::GlowingRock, Rgb::new(30, 187, 235)));
305                painter
306                    .sphere(Aabb {
307                        min: (center - 4).with_z(level_height + tower_height + 3),
308                        max: (center + 4).with_z(level_height + tower_height + 11),
309                    })
310                    .fill(glowing.clone());
311
312                // cargo
313                let cargo_pos = Vec2::new(center.x, center.y + 5);
314                for dir in CARDINALS.iter() {
315                    let sprite_pos = cargo_pos + dir;
316                    let rows = 1 + (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
317                    for r in 0..rows {
318                        painter
319                            .aabb(Aabb {
320                                min: (sprite_pos).with_z(level_height + r),
321                                max: (sprite_pos + 1).with_z(level_height + 1 + r),
322                            })
323                            .fill(Fill::Block(Block::air(
324                                match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2)
325                                    as i32
326                                {
327                                    0 => SpriteKind::Barrel,
328                                    _ => SpriteKind::CrateBlock,
329                                },
330                            )));
331                        if r > 1 {
332                            painter.owned_resource_sprite(
333                                sprite_pos.with_z(level_height + 1 + r),
334                                SpriteKind::Crate,
335                                0,
336                            );
337                        }
338                    }
339
340                    // gangway and dock
341                    let dock_pos = center + dir * 27;
342                    let rotation = -f32::atan2(dir.x as f32, dir.y as f32);
343
344                    painter
345                        .aabb(Aabb {
346                            min: Vec2::new(center.x - 4, center.y + bldg_size + 1)
347                                .with_z(level_height - 1),
348                            max: Vec2::new(center.x + 4, center.y + 27).with_z(level_height),
349                        })
350                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
351                        .fill(white.clone());
352                    painter
353                        .cylinder_with_radius(dock_pos.with_z(level_height), 5.0, 1.0)
354                        .fill(blue_broken.clone());
355                    painter
356                        .cylinder_with_radius(dock_pos.with_z(level_height - 1), 4.0, 2.0)
357                        .fill(white.clone());
358
359                    // suspension cables
360                    painter
361                        .line(
362                            Vec2::new(center.x - 4, center.y + 2)
363                                .with_z(level_height + tower_height),
364                            Vec2::new(center.x - 4, center.y + tower_height + 2)
365                                .with_z(level_height),
366                            0.75,
367                        )
368                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
369                        .fill(white.clone());
370                    painter
371                        .line(
372                            Vec2::new(center.x + 3, center.y + 2)
373                                .with_z(level_height + tower_height),
374                            Vec2::new(center.x + 3, center.y + tower_height + 2)
375                                .with_z(level_height),
376                            0.75,
377                        )
378                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
379                        .fill(white.clone());
380                }
381                // campfire
382                let campfire_pos = (center + Vec2::new(0, -3)).with_z(level_height);
383                painter.spawn(
384                    EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
385                        .into_special(SpecialEntity::Waypoint),
386                );
387            }
388            painter
389                .cylinder(Aabb {
390                    min: (center - bldg_size).with_z(bldg_base - 2),
391                    max: (center + bldg_size).with_z(level_height),
392                })
393                .fill(white.clone());
394        }
395        for r in 0..=4 {
396            let bldg_size = size - (room_offset * r);
397            let bldg_base = base + ((bldg_height + 2) * r);
398
399            let step_positions = place_circular(center, (bldg_size - 1) as f32, 14);
400            for (s, step_pos) in step_positions.enumerate() {
401                let step_size = (size / 3) - r;
402
403                painter
404                    .cylinder(Aabb {
405                        min: (step_pos - step_size).with_z(bldg_base - 2 + s as i32),
406                        max: (step_pos + step_size).with_z(bldg_base + 4 + s as i32),
407                    })
408                    .clear();
409                painter
410                    .cylinder(Aabb {
411                        min: (step_pos - step_size).with_z(bldg_base - 3 + s as i32),
412                        max: (step_pos + step_size).with_z(bldg_base - 2 + s as i32),
413                    })
414                    .fill(blue_broken.clone());
415                painter
416                    .cylinder(Aabb {
417                        min: (step_pos - step_size + 1).with_z(bldg_base - 4 + s as i32),
418                        max: (step_pos + step_size - 1).with_z(bldg_base - 2 + s as i32),
419                    })
420                    .fill(white.clone());
421            }
422            let lamp_positions = place_circular(center, (bldg_size + 1) as f32, 14);
423            for (l, lamp_pos) in lamp_positions.enumerate() {
424                if (RandomField::new(0).get(lamp_pos.with_z(base)) % 4) < 1 {
425                    painter
426                        .aabb(Aabb {
427                            min: (lamp_pos - 1).with_z(bldg_base - 3 + l as i32),
428                            max: (lamp_pos + 1).with_z(bldg_base - 2 + l as i32),
429                        })
430                        .fill(blue_broken.clone());
431
432                    painter.sprite(
433                        lamp_pos.with_z(bldg_base - 2 + l as i32),
434                        SpriteKind::FireBowlGround,
435                    );
436                }
437            }
438        }
439    }
440}