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