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 + 7);
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)) as i32 % 8;
123 let gradient_var_2 = RandomField::new(0).get(plot_center.with_z(base + 1)) as i32 % 10;
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)) as i32 % 6;
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 = RandomField::new(0).get((center - length).with_z(base)) as i32 % 10;
167 let y_offset = RandomField::new(0).get((center + length).with_z(base)) as i32 % 10;
168 let super_center =
169 Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
170 painter
172 .cubic_bezier(
173 super_center.with_z(floor_level + (height / 2)),
174 (super_center - x_offset).with_z(floor_level + height),
175 (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
176 super_center.with_z(floor_level + (2 * height)),
177 (length - 1) as f32,
178 )
179 .fill(brick.clone());
180 if s == (storeys - 1) {
181 for dir in LOCALITY {
182 let cone_pos = super_center + (dir * 2);
183 let cone_var =
184 4 + RandomField::new(0).get(cone_pos.with_z(base)) as i32 % 4;
185 painter
186 .cone_with_radius(
187 cone_pos.with_z(floor_level + (2 * height) + 5),
188 (length / 2) as f32,
189 (length + cone_var) as f32,
190 )
191 .fill(brick.clone());
192 }
193 }
194 if n == tubes as i32 {
196 if ground_entries < 1 && floor_level > (base - 6) {
198 for dir in CARDINALS {
199 let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
200 let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
201 painter
202 .line(
203 entry_pos_inner.with_z(floor_level + 6),
204 entry_pos_outer.with_z(base + 35),
205 6.0,
206 )
207 .clear();
208 }
209 let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
210 painter
211 .line(
212 door_start.with_z(floor_level + 2),
213 self.door_tile.with_z(base),
214 4.0,
215 )
216 .fill(wood.clone());
217 painter
218 .line(
219 door_start.with_z(floor_level + 7),
220 self.door_tile.with_z(base + 6),
221 7.0,
222 )
223 .clear();
224 ground_entries += 1;
225 }
226 painter
227 .cubic_bezier(
228 plot_center.with_z(floor_level + (height / 2)),
229 (plot_center - x_offset).with_z(floor_level + height),
230 (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
231 plot_center.with_z(floor_level + (2 * height)),
232 (length + 2) as f32,
233 )
234 .fill(brick.clone());
235 if s == (storeys - 1) {
237 let limit_up = painter.aabb(Aabb {
238 min: (plot_center - platform_length - 2).with_z(floor_level - 4),
239 max: (plot_center + platform_length + 2).with_z(floor_level + 1),
240 });
241 painter
242 .superquadric(
243 Aabb {
244 min: (plot_center - platform_length - 2)
245 .with_z(floor_level - 4),
246 max: (plot_center + platform_length + 2)
247 .with_z(floor_level + 6),
248 },
249 4.0,
250 )
251 .intersect(limit_up)
252 .fill(wood.clone());
253
254 let rotation = -f32::atan2(door_dir.x as f32, door_dir.y as f32);
256 painter
257 .aabb(Aabb {
258 min: Vec2::new(plot_center.x - 5, plot_center.y - 5)
259 .with_z(floor_level + 1),
260 max: Vec2::new(plot_center.x - 10, plot_center.y - 10)
261 .with_z(floor_level + 6),
262 })
263 .rotate_about(
264 Mat3::rotation_z(rotation).as_(),
265 plot_center.with_z(base),
266 )
267 .clear();
268 painter
269 .line(
270 Vec2::new(plot_center.x - 6, plot_center.y - 9)
271 .with_z(floor_level + 1),
272 Vec2::new(plot_center.x - 9, plot_center.y - 6)
273 .with_z(floor_level + 1),
274 0.5,
275 )
276 .rotate_about(
277 Mat3::rotation_z(rotation).as_(),
278 plot_center.with_z(base),
279 )
280 .fill(brick.clone());
281 painter
282 .line(
283 Vec2::new(plot_center.x - 10, plot_center.y - 10)
284 .with_z(floor_level + 1),
285 Vec2::new(plot_center.x - 10, plot_center.y - 10)
286 .with_z(floor_level + 6),
287 0.5,
288 )
289 .rotate_about(
290 Mat3::rotation_z(rotation).as_(),
291 plot_center.with_z(base),
292 )
293 .fill(brick.clone());
294 let agent_lantern_pos = Vec2::new(plot_center.x - 9, plot_center.y - 9);
295 let agent_lantern_pos_rotated = (plot_center.map(|i| i as f32)
296 + Mat2::rotation_z(rotation)
297 * (agent_lantern_pos - plot_center).map(|i| i as f32))
298 .map(|f| f.round() as i32);
299 painter.sprite(
300 agent_lantern_pos_rotated.with_z(floor_level + 5),
301 SpriteKind::WallLampMesa,
302 );
303
304 for dir in NEIGHBORS {
306 let lantern_pos = plot_center + (dir * (platform_length - 6));
307
308 painter.sprite(
309 lantern_pos.with_z(floor_level + 1),
310 SpriteKind::StreetLamp,
311 );
312 }
313 for dir in DIAGONALS {
314 let cargo_pos = plot_center + (dir * (2 * length));
315
316 for dir in CARDINALS {
317 let sprite_pos = cargo_pos + dir;
318 let rows =
319 (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
320 for r in 0..rows {
321 painter
322 .aabb(Aabb {
323 min: (sprite_pos).with_z(floor_level + 1 + r),
324 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
325 })
326 .fill(Fill::Block(Block::air(
327 match (RandomField::new(0)
328 .get(sprite_pos.with_z(base + r))
329 % 2)
330 as i32
331 {
332 0 => SpriteKind::Barrel,
333 _ => SpriteKind::CrateBlock,
334 },
335 )));
336 if r > 0 {
337 painter.owned_resource_sprite(
338 sprite_pos.with_z(floor_level + 2 + r),
339 SpriteKind::Crate,
340 0,
341 );
342 }
343 }
344 }
345 }
346 for dir in CARDINALS {
347 let dock_pos = plot_center + (dir * platform_length);
349
350 painter
351 .cylinder(Aabb {
352 min: (dock_pos - 8).with_z(floor_level),
353 max: (dock_pos + 8).with_z(floor_level + 1),
354 })
355 .fill(wood.clone());
356 painter
357 .cylinder(Aabb {
358 min: (dock_pos - 7).with_z(floor_level - 1),
359 max: (dock_pos + 7).with_z(floor_level),
360 })
361 .fill(wood.clone());
362 }
363 let campfire_pos =
365 Vec2::new(plot_center.x - platform_length - 2, plot_center.y)
366 .with_z(floor_level);
367 painter.spawn(
368 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
369 .into_special(SpecialEntity::Waypoint),
370 );
371 }
372
373 if floor_level > (base - 6) {
375 painter
377 .line(
378 Vec2::new(plot_center.x, plot_center.y - length)
379 .with_z(floor_level + 5),
380 Vec2::new(plot_center.x, plot_center.y + length)
381 .with_z(floor_level + 5),
382 4.0,
383 )
384 .fill(color.clone());
385 painter
386 .line(
387 Vec2::new(plot_center.x - length, plot_center.y)
388 .with_z(floor_level + 5),
389 Vec2::new(plot_center.x + length, plot_center.y)
390 .with_z(floor_level + 5),
391 4.0,
392 )
393 .fill(color.clone());
394 painter
396 .line(
397 Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
398 .with_z(floor_level + 4),
399 Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
400 .with_z(floor_level + 4),
401 4.0,
402 )
403 .clear();
404 painter
405 .line(
406 Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
407 .with_z(floor_level + 4),
408 Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
409 .with_z(floor_level + 4),
410 4.0,
411 )
412 .clear();
413 painter
414 .superquadric(
415 Aabb {
416 min: (plot_center - length - 1).with_z(floor_level),
417 max: (plot_center + length + 1)
418 .with_z(floor_level + height - 4),
419 },
420 sq_type,
421 )
422 .clear();
423 painter
425 .cylinder(Aabb {
426 min: (plot_center - length - 3).with_z(floor_level),
427 max: (plot_center + length + 3).with_z(floor_level + 1),
428 })
429 .fill(brick.clone());
430 painter
431 .cylinder(Aabb {
432 min: (plot_center - length + 1).with_z(floor_level),
433 max: (plot_center + length - 1).with_z(floor_level + 1),
434 })
435 .fill(color.clone());
436 painter
437 .cylinder(Aabb {
438 min: (plot_center - length + 2).with_z(floor_level),
439 max: (plot_center + length - 2).with_z(floor_level + 1),
440 })
441 .fill(brick.clone());
442 painter
444 .aabb(Aabb {
445 min: Vec2::new(plot_center.x - 3, plot_center.y + length)
446 .with_z(floor_level + 2),
447 max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
448 .with_z(floor_level + 7),
449 })
450 .fill(window2.clone());
451 painter
452 .aabb(Aabb {
453 min: Vec2::new(plot_center.x - 2, plot_center.y + length)
454 .with_z(floor_level + 2),
455 max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
456 .with_z(floor_level + 7),
457 })
458 .clear();
459
460 painter
461 .aabb(Aabb {
462 min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
463 .with_z(floor_level + 2),
464 max: Vec2::new(plot_center.x + 4, plot_center.y - length)
465 .with_z(floor_level + 7),
466 })
467 .fill(window2.clone());
468 painter
469 .aabb(Aabb {
470 min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
471 .with_z(floor_level + 2),
472 max: Vec2::new(plot_center.x + 3, plot_center.y - length)
473 .with_z(floor_level + 7),
474 })
475 .clear();
476 painter
477 .aabb(Aabb {
478 min: Vec2::new(plot_center.x + length, plot_center.y - 3)
479 .with_z(floor_level + 2),
480 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
481 .with_z(floor_level + 7),
482 })
483 .fill(window.clone());
484 painter
485 .aabb(Aabb {
486 min: Vec2::new(plot_center.x + length, plot_center.y - 2)
487 .with_z(floor_level + 2),
488 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
489 .with_z(floor_level + 7),
490 })
491 .clear();
492
493 painter
494 .aabb(Aabb {
495 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
496 .with_z(floor_level + 2),
497 max: Vec2::new(plot_center.x - length, plot_center.y + 4)
498 .with_z(floor_level + 7),
499 })
500 .fill(window.clone());
501 painter
502 .aabb(Aabb {
503 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
504 .with_z(floor_level + 2),
505 max: Vec2::new(plot_center.x - length, plot_center.y + 3)
506 .with_z(floor_level + 7),
507 })
508 .clear();
509 for dir in DIAGONALS {
511 let cargo_pos = plot_center + (dir * (length / 2));
512 for dir in CARDINALS {
513 let sprite_pos = cargo_pos + dir;
514 let rows =
515 (RandomField::new(0).get(sprite_pos.with_z(base)) % 4) as i32;
516 for r in 0..rows {
517 painter
518 .aabb(Aabb {
519 min: (sprite_pos).with_z(floor_level + 1 + r),
520 max: (sprite_pos + 1).with_z(floor_level + 2 + r),
521 })
522 .fill(Fill::Block(Block::air(
523 match (RandomField::new(0)
524 .get(sprite_pos.with_z(base + r))
525 % 2)
526 as i32
527 {
528 0 => SpriteKind::Barrel,
529 _ => SpriteKind::CrateBlock,
530 },
531 )));
532 }
533 }
534 }
535
536 let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
538 let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
539 for dir in SQUARE_4 {
540 let lamp_pos_1 = Vec2::new(
541 corner_pos_1.x + (dir.x * ((2 * length) - 1)),
542 corner_pos_1.y + (dir.y * 10),
543 )
544 .with_z(floor_level + 7);
545 painter.rotated_sprite(
546 lamp_pos_1,
547 SpriteKind::WallLampMesa,
548 (2 + (4 * dir.x)) as u8,
549 );
550 let lamp_pos_2 = Vec2::new(
551 corner_pos_2.x + (dir.x * 10),
552 corner_pos_2.y + (dir.y * ((2 * length) - 1)),
553 )
554 .with_z(floor_level + 7);
555 painter.rotated_sprite(
556 lamp_pos_2,
557 SpriteKind::WallLampMesa,
558 (4 - (4 * dir.y)) as u8,
559 );
560 }
561 }
562 if floor_level > (base + 8) {
564 let stairs_level = floor_level + 1;
565 let stairs_start = plot_center + door_dir * ((2 * length) - 7);
566 let mid_dir = if door_dir.x != 0 {
567 door_dir.x
568 } else {
569 door_dir.y
570 };
571 let stairs_mid = Vec2::new(
572 plot_center.x + mid_dir * (3 * (length / 2)),
573 plot_center.y + mid_dir * (3 * (length / 2)),
574 );
575 let stairs_end = Vec2::new(
576 plot_center.x + door_dir.y * ((2 * length) - 7),
577 plot_center.y + door_dir.x * ((2 * length) - 7),
578 );
579 let rope_pos = Vec2::new(
580 plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
581 plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
582 );
583
584 painter
585 .cylinder(Aabb {
586 min: (stairs_start - 6).with_z(stairs_level - 1),
587 max: (stairs_start + 6).with_z(stairs_level),
588 })
589 .fill(wood.clone());
590
591 painter
592 .cylinder(Aabb {
593 min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
594 max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
595 })
596 .fill(wood.clone());
597
598 painter
599 .cylinder(Aabb {
600 min: (stairs_end - 6).with_z(stairs_level - height - 1),
601 max: (stairs_end + 6).with_z(stairs_level - height),
602 })
603 .fill(wood.clone());
604
605 for n in 0..2 {
606 let stairs = painter
607 .line(
608 stairs_start.with_z(stairs_level + (n * 2)),
609 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
610 4.0 + (n as f32 / 2.0),
611 )
612 .union(painter.line(
613 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
614 stairs_end.with_z(stairs_level - height + (n * 2)),
615 4.0 + (n as f32 / 2.0),
616 ));
617 match n {
618 0 => stairs.fill(wood.clone()),
619 _ => stairs.clear(),
620 };
621 }
622 painter
623 .line(
624 rope_pos.with_z(stairs_level + (height / 2) - 3),
625 (plot_center - (length / 2))
626 .with_z(stairs_level + (height / 2) + 2),
627 1.5,
628 )
629 .fill(wood.clone());
630
631 painter
632 .aabb(Aabb {
633 min: rope_pos.with_z(stairs_level - (height / 2) - 1),
634 max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
635 })
636 .fill(rope.clone());
637 }
638 }
639 length += -1;
641 width += -1;
642 height += -1;
643 floor_level += height;
644 mem::swap(&mut length, &mut width);
645 }
646 }
647 }
648}