1use super::*;
2use crate::{
3 Land,
4 site::{generation::PrimitiveTransform, util::gradient::WrapMode},
5 util::{DIAGONALS, LOCALITY, NEIGHBORS, RandomField, Sampler, within_distance},
6};
7use common::{
8 generation::SpecialEntity,
9 terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::{f32::consts::TAU, mem};
13use vek::*;
14
15pub struct CliffTownAirshipDock {
17 pub door_tile: Vec2<i32>,
19 pub(crate) alt: i32,
21 door_dir: Vec2<i32>,
22 surface_color: Rgb<f32>,
23 sub_surface_color: Rgb<f32>,
24 pub center: Vec2<i32>,
25 variant: i32,
26 storeys: i32,
27 platform_length: i32,
28 pub docking_positions: Vec<Vec3<i32>>,
29}
30
31impl CliffTownAirshipDock {
32 pub fn generate(
33 land: &Land,
34 index: IndexRef,
35 _rng: &mut impl Rng,
36 site: &Site,
37 door_tile: Vec2<i32>,
38 door_dir: Vec2<i32>,
39 tile_aabr: Aabr<i32>,
40 ) -> Self {
41 let door_tile_pos = site.tile_center_wpos(door_tile);
42 let bounds = Aabr {
43 min: site.tile_wpos(tile_aabr.min),
44 max: site.tile_wpos(tile_aabr.max),
45 };
46 let center = bounds.center();
47
48 let mut max_surface_alt = i32::MIN;
57 for dx in -3..=3 {
59 for dy in -3..=3 {
60 let pos = center + Vec2::new(dx * 24, dy * 24);
61 let surface_alt = land.get_surface_alt_approx(pos) as i32;
62 if surface_alt > max_surface_alt {
63 max_surface_alt = surface_alt;
64 }
65 }
66 }
67
68 let mut min_foundation_alt = i32::MAX;
71 for dx in -2..=2 {
72 for dy in -2..=2 {
73 let pos = center + Vec2::new(dx * 15, dy * 15);
74 let alt = land.get_surface_alt_approx(pos) as i32;
75 if alt < min_foundation_alt {
76 min_foundation_alt = alt;
77 }
78 }
79 }
80
81 let variant = 15;
82 let storeys = 5 + (variant / 2);
83 let platform_length = 2 * variant;
84 let mut docking_positions = vec![];
85 let platform_height = 18 + variant / 2 - 1;
86 let mut base_alt = min_foundation_alt;
90 let mut platform_level = base_alt - platform_height + (storeys - 1) * platform_height
95 - (storeys - 2) * (storeys - 1) / 2;
96 let clearance = platform_level - (max_surface_alt + 6);
97 if clearance < 0 {
98 base_alt += -clearance;
99 platform_level += -clearance;
100 }
101 for dir in CARDINALS {
102 let docking_pos = center + dir * (platform_length + 9);
103 docking_positions.push(docking_pos.with_z(platform_level + 1));
104 }
105
106 let (surface_color, sub_surface_color) =
107 if let Some(sample) = land.column_sample(bounds.center(), index) {
108 (sample.surface_color, sample.sub_surface_color)
109 } else {
110 (Rgb::new(161.0, 116.0, 86.0), Rgb::new(88.0, 64.0, 64.0))
111 };
112 Self {
113 door_tile: door_tile_pos,
114 alt: base_alt,
115 door_dir,
116 surface_color,
117 sub_surface_color,
118 center,
119 variant,
120 storeys,
121 platform_length,
122 docking_positions,
123 }
124 }
125}
126
127impl Structure for CliffTownAirshipDock {
128 #[cfg(feature = "dyn-lib")]
129 #[unsafe(export_name = "as_dyn_structure_clifftownairshipdock")]
130 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
131 Some((
132 Self::as_dyn_impl(self),
133 "as_dyn_structure_clifftownairshipdock",
134 ))
135 }
136
137 fn spawn_rules_inner(
138 &self,
139 spawn_rules: &mut SpawnRules,
140 _land: &Land,
141 wpos: Vec2<i32>,
142 _weight: f32,
143 ) {
144 const AIRSHIP_MIN_TREE_DIST: i32 = 53;
149 spawn_rules.trees &= !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST);
150 spawn_rules.waypoints = false;
151 }
152
153 fn airship_dock_info(&self) -> Option<AirshipDockInfo<'_>> {
154 Some(AirshipDockInfo {
155 door_tile: self.door_tile,
156 center: self.center,
157 docking_positions: &self.docking_positions,
158 })
159 }
160
161 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
162 let base = self.alt;
163 let plot_center = self.center;
164 let door_dir = self.door_dir;
165
166 let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
167 let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
168 let gradient_center = Vec3::new(
169 plot_center.x as f32,
170 plot_center.y as f32,
171 (base + 1) as f32,
172 );
173 let gradient_var_1 = (RandomField::new(0).get(plot_center.with_z(base)) % 8) as i32;
174 let gradient_var_2 = (RandomField::new(0).get(plot_center.with_z(base + 1)) % 10) as i32;
175
176 let brick = Fill::Gradient(
177 util::gradient::Gradient::new(
178 gradient_center,
179 8.0 + gradient_var_1 as f32,
180 util::gradient::Shape::Point,
181 (surface_color, sub_surface_color),
182 )
183 .with_repeat(if gradient_var_2 > 5 {
184 WrapMode::Repeat
185 } else {
186 WrapMode::PingPong
187 }),
188 BlockKind::Rock,
189 );
190
191 let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12);
192 let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock));
193 let window = Fill::Block(Block::air(SpriteKind::WindowArabic));
194 let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap());
195 let rope = Fill::Block(Block::air(SpriteKind::Rope));
196
197 let tube_var = (RandomField::new(0).get(plot_center.with_z(base)) % 6) as i32;
198 let radius = 10.0 + tube_var as f32;
199 let tubes = 3.0 + tube_var as f32;
200 let phi = TAU / tubes;
201 for n in 1..=tubes as i32 {
202 let center = Vec2::new(
203 plot_center.x + (radius * ((n as f32 * phi).cos())) as i32,
204 plot_center.y + (radius * ((n as f32 * phi).sin())) as i32,
205 );
206 let sq_type = 3.5;
208 let storeys = self.storeys;
209 let variant = self.variant;
210 let mut length = 16 + (variant / 2);
211 let mut width = 7 * length / 8;
212 let mut height = 18 + variant / 2;
213 let mut floor_level = self.alt - height + 1;
214 let platform_length = self.platform_length;
215 let mut ground_entries = 0;
216 for s in 0..storeys {
217 let x_offset =
218 (RandomField::new(0).get((center - length).with_z(base)) % 10) as i32;
219 let y_offset =
220 (RandomField::new(0).get((center + length).with_z(base)) % 10) as i32;
221 let super_center =
222 Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
223 painter
225 .cubic_bezier(
226 super_center.with_z(floor_level + (height / 2)),
227 (super_center - x_offset).with_z(floor_level + height),
228 (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
229 super_center.with_z(floor_level + (2 * height)),
230 (length - 1) as f32,
231 )
232 .fill(brick.clone());
233 if s == (storeys - 1) {
234 for dir in LOCALITY {
235 let cone_pos = super_center + (dir * 2);
236 let cone_var =
237 4 + (RandomField::new(0).get(cone_pos.with_z(base)) % 4) as i32;
238 painter
239 .cone_with_radius(
240 cone_pos.with_z(floor_level + (2 * height) + 5),
241 (length / 2) as f32,
242 (length + cone_var) as f32,
243 )
244 .fill(brick.clone());
245 }
246 }
247 if n == tubes as i32 {
249 if ground_entries < 1 && floor_level > (base - 6) {
251 for dir in CARDINALS {
252 let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
253 let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
254 painter
255 .line(
256 entry_pos_inner.with_z(floor_level + 6),
257 entry_pos_outer.with_z(base + 35),
258 6.0,
259 )
260 .clear();
261 }
262 let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
263 painter
264 .line(
265 door_start.with_z(floor_level + 2),
266 self.door_tile.with_z(base),
267 4.0,
268 )
269 .fill(wood.clone());
270 painter
271 .line(
272 door_start.with_z(floor_level + 7),
273 self.door_tile.with_z(base + 6),
274 7.0,
275 )
276 .clear();
277 ground_entries += 1;
278 }
279 painter
280 .cubic_bezier(
281 plot_center.with_z(floor_level + (height / 2)),
282 (plot_center - x_offset).with_z(floor_level + height),
283 (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
284 plot_center.with_z(floor_level + (2 * height)),
285 (length + 2) as f32,
286 )
287 .fill(brick.clone());
288 if s == (storeys - 1) {
290 let limit_up = painter.aabb(Aabb {
291 min: (plot_center - platform_length - 2).with_z(floor_level - 4),
292 max: (plot_center + platform_length + 2).with_z(floor_level + 1),
293 });
294 painter
295 .superquadric(
296 Aabb {
297 min: (plot_center - platform_length - 2)
298 .with_z(floor_level - 4),
299 max: (plot_center + platform_length + 2)
300 .with_z(floor_level + 6),
301 },
302 4.0,
303 )
304 .intersect(limit_up)
305 .fill(wood.clone());
306
307 let rotation = -f32::atan2(door_dir.x as f32, door_dir.y as f32);
309 painter
310 .aabb(Aabb {
311 min: Vec2::new(plot_center.x - 5, plot_center.y - 5)
312 .with_z(floor_level + 1),
313 max: Vec2::new(plot_center.x - 10, plot_center.y - 10)
314 .with_z(floor_level + 6),
315 })
316 .rotate_about(
317 Mat3::rotation_z(rotation).as_(),
318 plot_center.with_z(base),
319 )
320 .clear();
321 painter
322 .line(
323 Vec2::new(plot_center.x - 6, plot_center.y - 9)
324 .with_z(floor_level + 1),
325 Vec2::new(plot_center.x - 9, plot_center.y - 6)
326 .with_z(floor_level + 1),
327 0.5,
328 )
329 .rotate_about(
330 Mat3::rotation_z(rotation).as_(),
331 plot_center.with_z(base),
332 )
333 .fill(brick.clone());
334 painter
335 .line(
336 Vec2::new(plot_center.x - 10, plot_center.y - 10)
337 .with_z(floor_level + 1),
338 Vec2::new(plot_center.x - 10, plot_center.y - 10)
339 .with_z(floor_level + 6),
340 0.5,
341 )
342 .rotate_about(
343 Mat3::rotation_z(rotation).as_(),
344 plot_center.with_z(base),
345 )
346 .fill(brick.clone());
347 let agent_lantern_pos = Vec2::new(plot_center.x - 9, plot_center.y - 9);
348 let agent_lantern_pos_rotated = (plot_center.map(|i| i as f32)
349 + Mat2::rotation_z(rotation)
350 * (agent_lantern_pos - plot_center).map(|i| i as f32))
351 .map(|f| f.round() as i32);
352 painter.sprite(
353 agent_lantern_pos_rotated.with_z(floor_level + 5),
354 SpriteKind::WallLampMesa,
355 );
356
357 for dir in NEIGHBORS {
359 let lantern_pos = plot_center + (dir * (platform_length - 6));
360
361 painter.sprite(
362 lantern_pos.with_z(floor_level + 1),
363 SpriteKind::StreetLamp,
364 );
365 }
366 for dir in DIAGONALS {
367 let cargo_pos = plot_center + (dir * (2 * length));
368
369 for dir in CARDINALS {
370 let sprite_pos = cargo_pos + dir;
371 let rows =
372 (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
373 for r in 0..rows {
374 painter
375 .aabb(Aabb {
376 min: (sprite_pos).with_z(floor_level + 1 + r),
377 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
378 })
379 .fill(Fill::Block(Block::air(
380 match (RandomField::new(0)
381 .get(sprite_pos.with_z(base + r))
382 % 2)
383 as i32
384 {
385 0 => SpriteKind::Barrel,
386 _ => SpriteKind::CrateBlock,
387 },
388 )));
389 if r > 0 {
390 painter.owned_resource_sprite(
391 sprite_pos.with_z(floor_level + 2 + r),
392 SpriteKind::Crate,
393 0,
394 );
395 }
396 }
397 }
398 }
399 for dir in CARDINALS {
400 let dock_pos = plot_center + (dir * platform_length);
402
403 painter
404 .cylinder(Aabb {
405 min: (dock_pos - 8).with_z(floor_level),
406 max: (dock_pos + 8).with_z(floor_level + 1),
407 })
408 .fill(wood.clone());
409 painter
410 .cylinder(Aabb {
411 min: (dock_pos - 7).with_z(floor_level - 1),
412 max: (dock_pos + 7).with_z(floor_level),
413 })
414 .fill(wood.clone());
415 }
416 let campfire_pos =
418 Vec2::new(plot_center.x - platform_length - 2, plot_center.y)
419 .with_z(floor_level);
420 painter.spawn(
421 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
422 .into_special(SpecialEntity::Waypoint),
423 );
424 }
425
426 if floor_level > (base - 6) {
428 painter
430 .line(
431 Vec2::new(plot_center.x, plot_center.y - length)
432 .with_z(floor_level + 5),
433 Vec2::new(plot_center.x, plot_center.y + length)
434 .with_z(floor_level + 5),
435 4.0,
436 )
437 .fill(color.clone());
438 painter
439 .line(
440 Vec2::new(plot_center.x - length, plot_center.y)
441 .with_z(floor_level + 5),
442 Vec2::new(plot_center.x + length, plot_center.y)
443 .with_z(floor_level + 5),
444 4.0,
445 )
446 .fill(color.clone());
447 painter
449 .line(
450 Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
451 .with_z(floor_level + 4),
452 Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
453 .with_z(floor_level + 4),
454 4.0,
455 )
456 .clear();
457 painter
458 .line(
459 Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
460 .with_z(floor_level + 4),
461 Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
462 .with_z(floor_level + 4),
463 4.0,
464 )
465 .clear();
466 painter
467 .superquadric(
468 Aabb {
469 min: (plot_center - length - 1).with_z(floor_level),
470 max: (plot_center + length + 1)
471 .with_z(floor_level + height - 4),
472 },
473 sq_type,
474 )
475 .clear();
476 painter
478 .cylinder(Aabb {
479 min: (plot_center - length - 3).with_z(floor_level),
480 max: (plot_center + length + 3).with_z(floor_level + 1),
481 })
482 .fill(brick.clone());
483 painter
484 .cylinder(Aabb {
485 min: (plot_center - length + 1).with_z(floor_level),
486 max: (plot_center + length - 1).with_z(floor_level + 1),
487 })
488 .fill(color.clone());
489 painter
490 .cylinder(Aabb {
491 min: (plot_center - length + 2).with_z(floor_level),
492 max: (plot_center + length - 2).with_z(floor_level + 1),
493 })
494 .fill(brick.clone());
495 painter
497 .aabb(Aabb {
498 min: Vec2::new(plot_center.x - 3, plot_center.y + length)
499 .with_z(floor_level + 2),
500 max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
501 .with_z(floor_level + 7),
502 })
503 .fill(window2.clone());
504 painter
505 .aabb(Aabb {
506 min: Vec2::new(plot_center.x - 2, plot_center.y + length)
507 .with_z(floor_level + 2),
508 max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
509 .with_z(floor_level + 7),
510 })
511 .clear();
512
513 painter
514 .aabb(Aabb {
515 min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
516 .with_z(floor_level + 2),
517 max: Vec2::new(plot_center.x + 4, plot_center.y - length)
518 .with_z(floor_level + 7),
519 })
520 .fill(window2.clone());
521 painter
522 .aabb(Aabb {
523 min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
524 .with_z(floor_level + 2),
525 max: Vec2::new(plot_center.x + 3, plot_center.y - length)
526 .with_z(floor_level + 7),
527 })
528 .clear();
529 painter
530 .aabb(Aabb {
531 min: Vec2::new(plot_center.x + length, plot_center.y - 3)
532 .with_z(floor_level + 2),
533 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
534 .with_z(floor_level + 7),
535 })
536 .fill(window.clone());
537 painter
538 .aabb(Aabb {
539 min: Vec2::new(plot_center.x + length, plot_center.y - 2)
540 .with_z(floor_level + 2),
541 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
542 .with_z(floor_level + 7),
543 })
544 .clear();
545
546 painter
547 .aabb(Aabb {
548 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
549 .with_z(floor_level + 2),
550 max: Vec2::new(plot_center.x - length, plot_center.y + 4)
551 .with_z(floor_level + 7),
552 })
553 .fill(window.clone());
554 painter
555 .aabb(Aabb {
556 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
557 .with_z(floor_level + 2),
558 max: Vec2::new(plot_center.x - length, plot_center.y + 3)
559 .with_z(floor_level + 7),
560 })
561 .clear();
562 for dir in DIAGONALS {
564 let cargo_pos = plot_center + (dir * (length / 2));
565 for dir in CARDINALS {
566 let sprite_pos = cargo_pos + dir;
567 let rows =
568 (RandomField::new(0).get(sprite_pos.with_z(base)) % 4) as i32;
569 for r in 0..rows {
570 painter
571 .aabb(Aabb {
572 min: (sprite_pos).with_z(floor_level + 1 + r),
573 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
574 })
575 .fill(Fill::Block(Block::air(
576 match (RandomField::new(0)
577 .get(sprite_pos.with_z(base + r))
578 % 2)
579 as i32
580 {
581 0 => SpriteKind::Barrel,
582 _ => SpriteKind::CrateBlock,
583 },
584 )));
585 }
586 }
587 }
588
589 let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
591 let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
592 for dir in SQUARE_4 {
593 let lamp_pos_1 = Vec2::new(
594 corner_pos_1.x + (dir.x * ((2 * length) - 1)),
595 corner_pos_1.y + (dir.y * 10),
596 )
597 .with_z(floor_level + 7);
598 painter.rotated_sprite(
599 lamp_pos_1,
600 SpriteKind::WallLampMesa,
601 (2 + (4 * dir.x)) as u8,
602 );
603 let lamp_pos_2 = Vec2::new(
604 corner_pos_2.x + (dir.x * 10),
605 corner_pos_2.y + (dir.y * ((2 * length) - 1)),
606 )
607 .with_z(floor_level + 7);
608 painter.rotated_sprite(
609 lamp_pos_2,
610 SpriteKind::WallLampMesa,
611 (4 - (4 * dir.y)) as u8,
612 );
613 }
614 }
615 if floor_level > (base + 8) {
617 let stairs_level = floor_level + 1;
618 let stairs_start = plot_center + door_dir * ((2 * length) - 7);
619 let mid_dir = if door_dir.x != 0 {
620 door_dir.x
621 } else {
622 door_dir.y
623 };
624 let stairs_mid = Vec2::new(
625 plot_center.x + mid_dir * (3 * (length / 2)),
626 plot_center.y + mid_dir * (3 * (length / 2)),
627 );
628 let stairs_end = Vec2::new(
629 plot_center.x + door_dir.y * ((2 * length) - 7),
630 plot_center.y + door_dir.x * ((2 * length) - 7),
631 );
632 let rope_pos = Vec2::new(
633 plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
634 plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
635 );
636
637 painter
638 .cylinder(Aabb {
639 min: (stairs_start - 6).with_z(stairs_level - 1),
640 max: (stairs_start + 6).with_z(stairs_level),
641 })
642 .fill(wood.clone());
643
644 painter
645 .cylinder(Aabb {
646 min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
647 max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
648 })
649 .fill(wood.clone());
650
651 painter
652 .cylinder(Aabb {
653 min: (stairs_end - 6).with_z(stairs_level - height - 1),
654 max: (stairs_end + 6).with_z(stairs_level - height),
655 })
656 .fill(wood.clone());
657
658 for n in 0..2 {
659 let stairs = painter
660 .line(
661 stairs_start.with_z(stairs_level + (n * 2)),
662 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
663 4.0 + (n as f32 / 2.0),
664 )
665 .union(painter.line(
666 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
667 stairs_end.with_z(stairs_level - height + (n * 2)),
668 4.0 + (n as f32 / 2.0),
669 ));
670 match n {
671 0 => stairs.fill(wood.clone()),
672 _ => stairs.clear(),
673 };
674 }
675 painter
676 .line(
677 rope_pos.with_z(stairs_level + (height / 2) - 3),
678 (plot_center - (length / 2))
679 .with_z(stairs_level + (height / 2) + 2),
680 1.5,
681 )
682 .fill(wood.clone());
683
684 painter
685 .aabb(Aabb {
686 min: rope_pos.with_z(stairs_level - (height / 2) - 1),
687 max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
688 })
689 .fill(rope.clone());
690 }
691 }
692 length += -1;
694 width += -1;
695 height += -1;
696 floor_level += height;
697 mem::swap(&mut length, &mut width);
698 }
699 }
700 }
701}