veloren_world/site/plot/
coastal_airship_dock.rs

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