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
15pub struct SavannahAirshipDock {
17 pub door_tile: Vec2<i32>,
19 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 let mut max_surface_alt = i32::MIN;
52 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}