Skip to main content

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
110impl Structure for CoastalAirshipDock {
111    #[cfg(feature = "dyn-lib")]
112    #[unsafe(export_name = "as_dyn_structure_coastalairshipdock")]
113    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
114        Some((
115            Self::as_dyn_impl(self),
116            "as_dyn_structure_coastalairshipdock",
117        ))
118    }
119
120    fn spawn_rules_inner(
121        &self,
122        spawn_rules: &mut SpawnRules,
123        _land: &Land,
124        wpos: Vec2<i32>,
125        _weight: f32,
126    ) {
127        // dock is 5 tiles = 30 blocks in radius
128        // airships are 39 blocks wide.
129        // Tree can be up to 20 blocks in radius.
130        // Don't allow trees within 30 + 39 + 20 = 89 blocks of the dock center
131        const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
132        spawn_rules.trees &= !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2);
133
134        const MIN_FLAT_DIST: f32 = 12.0;
135        const SLOPE_LENGTH: f32 = 16.0;
136        let dist = wpos.as_::<f32>().distance(self.center.as_());
137        let weight = (1.0 - (dist - MIN_FLAT_DIST).max(0.0) / SLOPE_LENGTH).max(0.0);
138        spawn_rules.prefer_alt(self.alt as f32, weight);
139    }
140
141    fn airship_dock_info(&self) -> Option<AirshipDockInfo<'_>> {
142        Some(AirshipDockInfo {
143            door_tile: self.door_tile,
144            center: self.center,
145            docking_positions: &self.docking_positions,
146        })
147    }
148
149    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
150        let base = self.base;
151        let center = self.center;
152        let white = Fill::Sampling(Arc::new(|center| {
153            Some(match (RandomField::new(0).get(center)) % 37 {
154                0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
155                9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
156                18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
157                27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
158                _ => Block::new(BlockKind::Rock, Rgb::new(255, 244, 193)),
159            })
160        }));
161        let blue_broken = Fill::Sampling(Arc::new(|center| {
162            Some(match (RandomField::new(0).get(center)) % 20 {
163                0 => Block::new(BlockKind::Rock, Rgb::new(30, 187, 235)),
164                _ => Block::new(BlockKind::Rock, Rgb::new(11, 146, 187)),
165            })
166        }));
167
168        let length = self.diameter / 2;
169        let width = (self.diameter / 2) - 1;
170        let height = 15;
171        // fence, blue gates
172        painter
173            .aabb(Aabb {
174                min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - 2),
175                max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 1),
176            })
177            .fill(blue_broken.clone());
178
179        for dir in CARDINALS {
180            let frame_pos = Vec2::new(
181                center.x + dir.x * (length + 5),
182                center.y + dir.y * (width + 5),
183            );
184            painter
185                .line(center.with_z(base - 1), frame_pos.with_z(base - 1), 3.0)
186                .fill(blue_broken.clone());
187        }
188        // foundation
189        painter
190            .aabb(Aabb {
191                min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - height),
192                max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 2),
193            })
194            .fill(white.clone());
195        for f in 0..8 {
196            painter
197                .aabb(Aabb {
198                    min: Vec2::new(center.x - length - 7 - f, center.y - width - 7 - f)
199                        .with_z(base - 3 - f),
200                    max: Vec2::new(center.x + length + 8 + f, center.y + width + 8 + f)
201                        .with_z(base - 2 - f),
202                })
203                .fill(white.clone());
204        }
205        // clear yard
206        painter
207            .aabb(Aabb {
208                min: Vec2::new(center.x - length - 5, center.y - width - 5).with_z(base - 2),
209                max: Vec2::new(center.x + length + 6, center.y + width + 6).with_z(base + height),
210            })
211            .clear();
212        // clear entries
213        for dir in CARDINALS {
214            let clear_pos = Vec2::new(
215                center.x + dir.x * (length + 7),
216                center.y + dir.y * (width + 7),
217            );
218            painter
219                .line(center.with_z(base - 1), clear_pos.with_z(base - 1), 2.0)
220                .clear();
221        }
222
223        // rooms
224        let size = self.size;
225        let room_offset = size / 6;
226        let bldg_height = self.bldg_height;
227        let tower_height = (bldg_height as f32 * 1.5).round() as i32;
228        for r in 0..=4 {
229            let bldg_size = size - (room_offset * r);
230            let bldg_base = base + ((bldg_height + 2) * r);
231            let level_height = bldg_base + bldg_height;
232            if r == 4 {
233                // Center platform
234                painter
235                    .cylinder_with_radius(
236                        center.with_z(level_height - 1),
237                        (bldg_size + 5) as f32,
238                        1.0,
239                    )
240                    .fill(white.clone());
241                // blue walls
242                painter
243                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 1.0)
244                    .fill(blue_broken.clone());
245
246                // Agent Desk
247
248                // Agent booth back wall, less than ~ 1 quadrant
249                let agent_booth_mask = painter.aabb(Aabb {
250                    min: (Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size - 4)))
251                        .with_z(level_height),
252                    max: (Vec2::new(center.x - (bldg_size + 5), center.y + (bldg_size + 5)))
253                        .with_z(level_height + 2),
254                });
255                painter
256                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 2.0)
257                    .intersect(agent_booth_mask)
258                    .fill(blue_broken.clone());
259
260                // Clear top
261                painter
262                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 4) as f32, 2.0)
263                    .clear();
264
265                // Agent booth front wall
266                painter
267                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 2) as f32, 1.0)
268                    .intersect(agent_booth_mask)
269                    .fill(blue_broken.clone());
270                // Clear excess Agent booth front wall
271                painter
272                    .cylinder_with_radius(center.with_z(level_height), (bldg_size + 1) as f32, 1.0)
273                    .intersect(agent_booth_mask)
274                    .clear();
275                // Agent booth connecting walls
276                painter
277                    .line(
278                        Vec2::new(center.x - (bldg_size + 3), center.y + (bldg_size - 5))
279                            .with_z(level_height),
280                        Vec2::new(center.x - (bldg_size + 2), center.y + (bldg_size - 5))
281                            .with_z(level_height),
282                        0.5,
283                    )
284                    .fill(blue_broken.clone());
285                painter
286                    .line(
287                        Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 1))
288                            .with_z(level_height),
289                        Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 2))
290                            .with_z(level_height),
291                        0.5,
292                    )
293                    .fill(blue_broken.clone());
294
295                // Clear the gangways
296                painter
297                    .aabb(Aabb {
298                        min: Vec2::new(center.x - 3, center.y + bldg_size * 2).with_z(level_height),
299                        max: Vec2::new(center.x + 3, center.y - (bldg_size * 2))
300                            .with_z(level_height + 1),
301                    })
302                    .clear();
303                painter
304                    .aabb(Aabb {
305                        min: Vec2::new(center.x - bldg_size * 2, center.y - 3).with_z(level_height),
306                        max: Vec2::new(center.x + bldg_size * 2, center.y + 3)
307                            .with_z(level_height + 1),
308                    })
309                    .clear();
310
311                // Cable Tower
312                painter
313                    .cylinder_with_radius(center.with_z(level_height), 1.0, tower_height as f32)
314                    .fill(white.clone());
315                painter
316                    .cone_with_radius(center.with_z(level_height + tower_height), 4.0, 3.0)
317                    .fill(white.clone());
318
319                let glowing =
320                    Fill::Block(Block::new(BlockKind::GlowingRock, Rgb::new(30, 187, 235)));
321                painter
322                    .sphere(Aabb {
323                        min: (center - 4).with_z(level_height + tower_height + 3),
324                        max: (center + 4).with_z(level_height + tower_height + 11),
325                    })
326                    .fill(glowing.clone());
327
328                // cargo
329                let cargo_pos = Vec2::new(center.x, center.y + 5);
330                for dir in CARDINALS.iter() {
331                    let sprite_pos = cargo_pos + dir;
332                    let rows = 1 + (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
333                    for r in 0..rows {
334                        painter
335                            .aabb(Aabb {
336                                min: (sprite_pos).with_z(level_height + r),
337                                max: (sprite_pos + 1).with_z(level_height + 1 + r),
338                            })
339                            .fill(Fill::Block(Block::air(
340                                match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2)
341                                    as i32
342                                {
343                                    0 => SpriteKind::Barrel,
344                                    _ => SpriteKind::CrateBlock,
345                                },
346                            )));
347                        if r > 1 {
348                            painter.owned_resource_sprite(
349                                sprite_pos.with_z(level_height + 1 + r),
350                                SpriteKind::Crate,
351                                0,
352                            );
353                        }
354                    }
355
356                    // gangway and dock
357                    let dock_pos = center + dir * 27;
358                    let rotation = -f32::atan2(dir.x as f32, dir.y as f32);
359
360                    painter
361                        .aabb(Aabb {
362                            min: Vec2::new(center.x - 4, center.y + bldg_size + 1)
363                                .with_z(level_height - 1),
364                            max: Vec2::new(center.x + 4, center.y + 27).with_z(level_height),
365                        })
366                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
367                        .fill(white.clone());
368                    painter
369                        .cylinder_with_radius(dock_pos.with_z(level_height), 5.0, 1.0)
370                        .fill(blue_broken.clone());
371                    painter
372                        .cylinder_with_radius(dock_pos.with_z(level_height - 1), 4.0, 2.0)
373                        .fill(white.clone());
374
375                    // suspension cables
376                    painter
377                        .line(
378                            Vec2::new(center.x - 4, center.y + 2)
379                                .with_z(level_height + tower_height),
380                            Vec2::new(center.x - 4, center.y + tower_height + 2)
381                                .with_z(level_height),
382                            0.75,
383                        )
384                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
385                        .fill(white.clone());
386                    painter
387                        .line(
388                            Vec2::new(center.x + 3, center.y + 2)
389                                .with_z(level_height + tower_height),
390                            Vec2::new(center.x + 3, center.y + tower_height + 2)
391                                .with_z(level_height),
392                            0.75,
393                        )
394                        .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
395                        .fill(white.clone());
396                }
397                // campfire
398                let campfire_pos = (center + Vec2::new(0, -3)).with_z(level_height);
399                painter.spawn(
400                    EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
401                        .into_special(SpecialEntity::Waypoint),
402                );
403            }
404            painter
405                .cylinder(Aabb {
406                    min: (center - bldg_size).with_z(bldg_base - 2),
407                    max: (center + bldg_size).with_z(level_height),
408                })
409                .fill(white.clone());
410        }
411        for r in 0..=4 {
412            let bldg_size = size - (room_offset * r);
413            let bldg_base = base + ((bldg_height + 2) * r);
414
415            let step_positions = place_circular(center, (bldg_size - 1) as f32, 14);
416            for (s, step_pos) in step_positions.enumerate() {
417                let step_size = (size / 3) - r;
418
419                painter
420                    .cylinder(Aabb {
421                        min: (step_pos - step_size).with_z(bldg_base - 2 + s as i32),
422                        max: (step_pos + step_size).with_z(bldg_base + 4 + s as i32),
423                    })
424                    .clear();
425                painter
426                    .cylinder(Aabb {
427                        min: (step_pos - step_size).with_z(bldg_base - 3 + s as i32),
428                        max: (step_pos + step_size).with_z(bldg_base - 2 + s as i32),
429                    })
430                    .fill(blue_broken.clone());
431                painter
432                    .cylinder(Aabb {
433                        min: (step_pos - step_size + 1).with_z(bldg_base - 4 + s as i32),
434                        max: (step_pos + step_size - 1).with_z(bldg_base - 2 + s as i32),
435                    })
436                    .fill(white.clone());
437            }
438            let lamp_positions = place_circular(center, (bldg_size + 1) as f32, 14);
439            for (l, lamp_pos) in lamp_positions.enumerate() {
440                if (RandomField::new(0).get(lamp_pos.with_z(base)) % 4) < 1 {
441                    painter
442                        .aabb(Aabb {
443                            min: (lamp_pos - 1).with_z(bldg_base - 3 + l as i32),
444                            max: (lamp_pos + 1).with_z(bldg_base - 2 + l as i32),
445                        })
446                        .fill(blue_broken.clone());
447
448                    painter.sprite(
449                        lamp_pos.with_z(bldg_base - 2 + l as i32),
450                        SpriteKind::FireBowlGround,
451                    );
452                }
453            }
454        }
455    }
456}