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 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
127 SpawnRules {
128 trees: {
129 const AIRSHIP_MIN_TREE_DIST2: i32 = 53;
134 !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
135 },
136 waypoints: false,
137 ..SpawnRules::default()
138 }
139 }
140}
141
142impl Structure for CliffTownAirshipDock {
143 #[cfg(feature = "use-dyn-lib")]
144 const UPDATE_FN: &'static [u8] = b"render_cliff_town_airship_dock\0";
145
146 #[cfg_attr(
147 feature = "be-dyn-lib",
148 unsafe(export_name = "render_cliff_town_airship_dock")
149 )]
150 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
151 let base = self.alt;
152 let plot_center = self.center;
153 let door_dir = self.door_dir;
154
155 let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
156 let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
157 let gradient_center = Vec3::new(
158 plot_center.x as f32,
159 plot_center.y as f32,
160 (base + 1) as f32,
161 );
162 let gradient_var_1 = (RandomField::new(0).get(plot_center.with_z(base)) % 8) as i32;
163 let gradient_var_2 = (RandomField::new(0).get(plot_center.with_z(base + 1)) % 10) as i32;
164
165 let brick = Fill::Gradient(
166 util::gradient::Gradient::new(
167 gradient_center,
168 8.0 + gradient_var_1 as f32,
169 util::gradient::Shape::Point,
170 (surface_color, sub_surface_color),
171 )
172 .with_repeat(if gradient_var_2 > 5 {
173 WrapMode::Repeat
174 } else {
175 WrapMode::PingPong
176 }),
177 BlockKind::Rock,
178 );
179
180 let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12);
181 let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock));
182 let window = Fill::Block(Block::air(SpriteKind::WindowArabic));
183 let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap());
184 let rope = Fill::Block(Block::air(SpriteKind::Rope));
185
186 let tube_var = (RandomField::new(0).get(plot_center.with_z(base)) % 6) as i32;
187 let radius = 10.0 + tube_var as f32;
188 let tubes = 3.0 + tube_var as f32;
189 let phi = TAU / tubes;
190 for n in 1..=tubes as i32 {
191 let center = Vec2::new(
192 plot_center.x + (radius * ((n as f32 * phi).cos())) as i32,
193 plot_center.y + (radius * ((n as f32 * phi).sin())) as i32,
194 );
195 let sq_type = 3.5;
197 let storeys = self.storeys;
198 let variant = self.variant;
199 let mut length = 16 + (variant / 2);
200 let mut width = 7 * length / 8;
201 let mut height = 18 + variant / 2;
202 let mut floor_level = self.alt - height + 1;
203 let platform_length = self.platform_length;
204 let mut ground_entries = 0;
205 for s in 0..storeys {
206 let x_offset =
207 (RandomField::new(0).get((center - length).with_z(base)) % 10) as i32;
208 let y_offset =
209 (RandomField::new(0).get((center + length).with_z(base)) % 10) as i32;
210 let super_center =
211 Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
212 painter
214 .cubic_bezier(
215 super_center.with_z(floor_level + (height / 2)),
216 (super_center - x_offset).with_z(floor_level + height),
217 (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
218 super_center.with_z(floor_level + (2 * height)),
219 (length - 1) as f32,
220 )
221 .fill(brick.clone());
222 if s == (storeys - 1) {
223 for dir in LOCALITY {
224 let cone_pos = super_center + (dir * 2);
225 let cone_var =
226 4 + (RandomField::new(0).get(cone_pos.with_z(base)) % 4) as i32;
227 painter
228 .cone_with_radius(
229 cone_pos.with_z(floor_level + (2 * height) + 5),
230 (length / 2) as f32,
231 (length + cone_var) as f32,
232 )
233 .fill(brick.clone());
234 }
235 }
236 if n == tubes as i32 {
238 if ground_entries < 1 && floor_level > (base - 6) {
240 for dir in CARDINALS {
241 let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
242 let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
243 painter
244 .line(
245 entry_pos_inner.with_z(floor_level + 6),
246 entry_pos_outer.with_z(base + 35),
247 6.0,
248 )
249 .clear();
250 }
251 let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
252 painter
253 .line(
254 door_start.with_z(floor_level + 2),
255 self.door_tile.with_z(base),
256 4.0,
257 )
258 .fill(wood.clone());
259 painter
260 .line(
261 door_start.with_z(floor_level + 7),
262 self.door_tile.with_z(base + 6),
263 7.0,
264 )
265 .clear();
266 ground_entries += 1;
267 }
268 painter
269 .cubic_bezier(
270 plot_center.with_z(floor_level + (height / 2)),
271 (plot_center - x_offset).with_z(floor_level + height),
272 (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
273 plot_center.with_z(floor_level + (2 * height)),
274 (length + 2) as f32,
275 )
276 .fill(brick.clone());
277 if s == (storeys - 1) {
279 let limit_up = painter.aabb(Aabb {
280 min: (plot_center - platform_length - 2).with_z(floor_level - 4),
281 max: (plot_center + platform_length + 2).with_z(floor_level + 1),
282 });
283 painter
284 .superquadric(
285 Aabb {
286 min: (plot_center - platform_length - 2)
287 .with_z(floor_level - 4),
288 max: (plot_center + platform_length + 2)
289 .with_z(floor_level + 6),
290 },
291 4.0,
292 )
293 .intersect(limit_up)
294 .fill(wood.clone());
295
296 let rotation = -f32::atan2(door_dir.x as f32, door_dir.y as f32);
298 painter
299 .aabb(Aabb {
300 min: Vec2::new(plot_center.x - 5, plot_center.y - 5)
301 .with_z(floor_level + 1),
302 max: Vec2::new(plot_center.x - 10, plot_center.y - 10)
303 .with_z(floor_level + 6),
304 })
305 .rotate_about(
306 Mat3::rotation_z(rotation).as_(),
307 plot_center.with_z(base),
308 )
309 .clear();
310 painter
311 .line(
312 Vec2::new(plot_center.x - 6, plot_center.y - 9)
313 .with_z(floor_level + 1),
314 Vec2::new(plot_center.x - 9, plot_center.y - 6)
315 .with_z(floor_level + 1),
316 0.5,
317 )
318 .rotate_about(
319 Mat3::rotation_z(rotation).as_(),
320 plot_center.with_z(base),
321 )
322 .fill(brick.clone());
323 painter
324 .line(
325 Vec2::new(plot_center.x - 10, plot_center.y - 10)
326 .with_z(floor_level + 1),
327 Vec2::new(plot_center.x - 10, plot_center.y - 10)
328 .with_z(floor_level + 6),
329 0.5,
330 )
331 .rotate_about(
332 Mat3::rotation_z(rotation).as_(),
333 plot_center.with_z(base),
334 )
335 .fill(brick.clone());
336 let agent_lantern_pos = Vec2::new(plot_center.x - 9, plot_center.y - 9);
337 let agent_lantern_pos_rotated = (plot_center.map(|i| i as f32)
338 + Mat2::rotation_z(rotation)
339 * (agent_lantern_pos - plot_center).map(|i| i as f32))
340 .map(|f| f.round() as i32);
341 painter.sprite(
342 agent_lantern_pos_rotated.with_z(floor_level + 5),
343 SpriteKind::WallLampMesa,
344 );
345
346 for dir in NEIGHBORS {
348 let lantern_pos = plot_center + (dir * (platform_length - 6));
349
350 painter.sprite(
351 lantern_pos.with_z(floor_level + 1),
352 SpriteKind::StreetLamp,
353 );
354 }
355 for dir in DIAGONALS {
356 let cargo_pos = plot_center + (dir * (2 * length));
357
358 for dir in CARDINALS {
359 let sprite_pos = cargo_pos + dir;
360 let rows =
361 (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
362 for r in 0..rows {
363 painter
364 .aabb(Aabb {
365 min: (sprite_pos).with_z(floor_level + 1 + r),
366 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
367 })
368 .fill(Fill::Block(Block::air(
369 match (RandomField::new(0)
370 .get(sprite_pos.with_z(base + r))
371 % 2)
372 as i32
373 {
374 0 => SpriteKind::Barrel,
375 _ => SpriteKind::CrateBlock,
376 },
377 )));
378 if r > 0 {
379 painter.owned_resource_sprite(
380 sprite_pos.with_z(floor_level + 2 + r),
381 SpriteKind::Crate,
382 0,
383 );
384 }
385 }
386 }
387 }
388 for dir in CARDINALS {
389 let dock_pos = plot_center + (dir * platform_length);
391
392 painter
393 .cylinder(Aabb {
394 min: (dock_pos - 8).with_z(floor_level),
395 max: (dock_pos + 8).with_z(floor_level + 1),
396 })
397 .fill(wood.clone());
398 painter
399 .cylinder(Aabb {
400 min: (dock_pos - 7).with_z(floor_level - 1),
401 max: (dock_pos + 7).with_z(floor_level),
402 })
403 .fill(wood.clone());
404 }
405 let campfire_pos =
407 Vec2::new(plot_center.x - platform_length - 2, plot_center.y)
408 .with_z(floor_level);
409 painter.spawn(
410 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
411 .into_special(SpecialEntity::Waypoint),
412 );
413 }
414
415 if floor_level > (base - 6) {
417 painter
419 .line(
420 Vec2::new(plot_center.x, plot_center.y - length)
421 .with_z(floor_level + 5),
422 Vec2::new(plot_center.x, plot_center.y + length)
423 .with_z(floor_level + 5),
424 4.0,
425 )
426 .fill(color.clone());
427 painter
428 .line(
429 Vec2::new(plot_center.x - length, plot_center.y)
430 .with_z(floor_level + 5),
431 Vec2::new(plot_center.x + length, plot_center.y)
432 .with_z(floor_level + 5),
433 4.0,
434 )
435 .fill(color.clone());
436 painter
438 .line(
439 Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
440 .with_z(floor_level + 4),
441 Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
442 .with_z(floor_level + 4),
443 4.0,
444 )
445 .clear();
446 painter
447 .line(
448 Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
449 .with_z(floor_level + 4),
450 Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
451 .with_z(floor_level + 4),
452 4.0,
453 )
454 .clear();
455 painter
456 .superquadric(
457 Aabb {
458 min: (plot_center - length - 1).with_z(floor_level),
459 max: (plot_center + length + 1)
460 .with_z(floor_level + height - 4),
461 },
462 sq_type,
463 )
464 .clear();
465 painter
467 .cylinder(Aabb {
468 min: (plot_center - length - 3).with_z(floor_level),
469 max: (plot_center + length + 3).with_z(floor_level + 1),
470 })
471 .fill(brick.clone());
472 painter
473 .cylinder(Aabb {
474 min: (plot_center - length + 1).with_z(floor_level),
475 max: (plot_center + length - 1).with_z(floor_level + 1),
476 })
477 .fill(color.clone());
478 painter
479 .cylinder(Aabb {
480 min: (plot_center - length + 2).with_z(floor_level),
481 max: (plot_center + length - 2).with_z(floor_level + 1),
482 })
483 .fill(brick.clone());
484 painter
486 .aabb(Aabb {
487 min: Vec2::new(plot_center.x - 3, plot_center.y + length)
488 .with_z(floor_level + 2),
489 max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
490 .with_z(floor_level + 7),
491 })
492 .fill(window2.clone());
493 painter
494 .aabb(Aabb {
495 min: Vec2::new(plot_center.x - 2, plot_center.y + length)
496 .with_z(floor_level + 2),
497 max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
498 .with_z(floor_level + 7),
499 })
500 .clear();
501
502 painter
503 .aabb(Aabb {
504 min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
505 .with_z(floor_level + 2),
506 max: Vec2::new(plot_center.x + 4, plot_center.y - length)
507 .with_z(floor_level + 7),
508 })
509 .fill(window2.clone());
510 painter
511 .aabb(Aabb {
512 min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
513 .with_z(floor_level + 2),
514 max: Vec2::new(plot_center.x + 3, plot_center.y - length)
515 .with_z(floor_level + 7),
516 })
517 .clear();
518 painter
519 .aabb(Aabb {
520 min: Vec2::new(plot_center.x + length, plot_center.y - 3)
521 .with_z(floor_level + 2),
522 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
523 .with_z(floor_level + 7),
524 })
525 .fill(window.clone());
526 painter
527 .aabb(Aabb {
528 min: Vec2::new(plot_center.x + length, plot_center.y - 2)
529 .with_z(floor_level + 2),
530 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
531 .with_z(floor_level + 7),
532 })
533 .clear();
534
535 painter
536 .aabb(Aabb {
537 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
538 .with_z(floor_level + 2),
539 max: Vec2::new(plot_center.x - length, plot_center.y + 4)
540 .with_z(floor_level + 7),
541 })
542 .fill(window.clone());
543 painter
544 .aabb(Aabb {
545 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
546 .with_z(floor_level + 2),
547 max: Vec2::new(plot_center.x - length, plot_center.y + 3)
548 .with_z(floor_level + 7),
549 })
550 .clear();
551 for dir in DIAGONALS {
553 let cargo_pos = plot_center + (dir * (length / 2));
554 for dir in CARDINALS {
555 let sprite_pos = cargo_pos + dir;
556 let rows =
557 (RandomField::new(0).get(sprite_pos.with_z(base)) % 4) as i32;
558 for r in 0..rows {
559 painter
560 .aabb(Aabb {
561 min: (sprite_pos).with_z(floor_level + 1 + r),
562 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
563 })
564 .fill(Fill::Block(Block::air(
565 match (RandomField::new(0)
566 .get(sprite_pos.with_z(base + r))
567 % 2)
568 as i32
569 {
570 0 => SpriteKind::Barrel,
571 _ => SpriteKind::CrateBlock,
572 },
573 )));
574 }
575 }
576 }
577
578 let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
580 let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
581 for dir in SQUARE_4 {
582 let lamp_pos_1 = Vec2::new(
583 corner_pos_1.x + (dir.x * ((2 * length) - 1)),
584 corner_pos_1.y + (dir.y * 10),
585 )
586 .with_z(floor_level + 7);
587 painter.rotated_sprite(
588 lamp_pos_1,
589 SpriteKind::WallLampMesa,
590 (2 + (4 * dir.x)) as u8,
591 );
592 let lamp_pos_2 = Vec2::new(
593 corner_pos_2.x + (dir.x * 10),
594 corner_pos_2.y + (dir.y * ((2 * length) - 1)),
595 )
596 .with_z(floor_level + 7);
597 painter.rotated_sprite(
598 lamp_pos_2,
599 SpriteKind::WallLampMesa,
600 (4 - (4 * dir.y)) as u8,
601 );
602 }
603 }
604 if floor_level > (base + 8) {
606 let stairs_level = floor_level + 1;
607 let stairs_start = plot_center + door_dir * ((2 * length) - 7);
608 let mid_dir = if door_dir.x != 0 {
609 door_dir.x
610 } else {
611 door_dir.y
612 };
613 let stairs_mid = Vec2::new(
614 plot_center.x + mid_dir * (3 * (length / 2)),
615 plot_center.y + mid_dir * (3 * (length / 2)),
616 );
617 let stairs_end = Vec2::new(
618 plot_center.x + door_dir.y * ((2 * length) - 7),
619 plot_center.y + door_dir.x * ((2 * length) - 7),
620 );
621 let rope_pos = Vec2::new(
622 plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
623 plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
624 );
625
626 painter
627 .cylinder(Aabb {
628 min: (stairs_start - 6).with_z(stairs_level - 1),
629 max: (stairs_start + 6).with_z(stairs_level),
630 })
631 .fill(wood.clone());
632
633 painter
634 .cylinder(Aabb {
635 min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
636 max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
637 })
638 .fill(wood.clone());
639
640 painter
641 .cylinder(Aabb {
642 min: (stairs_end - 6).with_z(stairs_level - height - 1),
643 max: (stairs_end + 6).with_z(stairs_level - height),
644 })
645 .fill(wood.clone());
646
647 for n in 0..2 {
648 let stairs = painter
649 .line(
650 stairs_start.with_z(stairs_level + (n * 2)),
651 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
652 4.0 + (n as f32 / 2.0),
653 )
654 .union(painter.line(
655 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
656 stairs_end.with_z(stairs_level - height + (n * 2)),
657 4.0 + (n as f32 / 2.0),
658 ));
659 match n {
660 0 => stairs.fill(wood.clone()),
661 _ => stairs.clear(),
662 };
663 }
664 painter
665 .line(
666 rope_pos.with_z(stairs_level + (height / 2) - 3),
667 (plot_center - (length / 2))
668 .with_z(stairs_level + (height / 2) + 2),
669 1.5,
670 )
671 .fill(wood.clone());
672
673 painter
674 .aabb(Aabb {
675 min: rope_pos.with_z(stairs_level - (height / 2) - 1),
676 max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
677 })
678 .fill(rope.clone());
679 }
680 }
681 length += -1;
683 width += -1;
684 height += -1;
685 floor_level += height;
686 mem::swap(&mut length, &mut width);
687 }
688 }
689 }
690}