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 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
103 SpawnRules {
104 trees: {
105 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}