1use super::*;
2use crate::{
3 Land,
4 site::generation::{PrimitiveTransform, place_circular},
5 util::{CARDINALS, RandomField, Sampler, within_distance},
6};
7use common::{
8 generation::SpecialEntity,
9 terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::sync::Arc;
13use vek::*;
14
15pub struct CoastalAirshipDock {
17 pub door_tile: Vec2<i32>,
19 pub(crate) alt: i32,
21 base: i32,
22 pub center: Vec2<i32>,
23 size: i32,
24 bldg_height: i32,
25 diameter: i32,
26 pub docking_positions: Vec<Vec3<i32>>,
27}
28
29impl CoastalAirshipDock {
30 pub fn generate(
31 land: &Land,
32 _rng: &mut impl Rng,
33 site: &Site,
34 door_tile: Vec2<i32>,
35 tile_aabr: Aabr<i32>,
36 ) -> Self {
37 let door_tile_pos = site.tile_center_wpos(door_tile);
38 let bounds = Aabr {
39 min: site.tile_wpos(tile_aabr.min),
40 max: site.tile_wpos(tile_aabr.max),
41 };
42 let diameter = (bounds.max.x - bounds.min.x).min(bounds.max.y - bounds.min.y);
43 let center = bounds.center();
44
45 let mut max_surface_alt = i32::MIN;
54 for dx in -3..=3 {
56 for dy in -3..=3 {
57 let pos = center + Vec2::new(dx * 24, dy * 24);
58 let alt = land.get_surface_alt_approx(pos) as i32;
59 if alt > max_surface_alt {
60 max_surface_alt = alt;
61 }
62 }
63 }
64
65 let foundation_qtr = diameter / 4;
69 let mut min_foundation_alt = i32::MAX;
70 for dx in -2..=2 {
71 for dy in -2..=2 {
72 let pos = center + Vec2::new(dx * foundation_qtr, dy * foundation_qtr);
73 let alt = land.get_surface_alt_approx(pos) as i32;
74 if alt < min_foundation_alt {
75 min_foundation_alt = alt;
76 }
77 }
78 }
79
80 let mut alt = min_foundation_alt + 2;
81 let size = 20;
82 let bldg_height = 12;
83 let mut base = alt + 1;
84 let mut top_floor = base + (bldg_height * 6) - 3;
85
86 let clearance = top_floor - (max_surface_alt + 6);
87 if clearance < 0 {
88 alt += -clearance;
89 base += -clearance;
90 top_floor += -clearance
91 }
92
93 let docking_positions = CARDINALS
94 .iter()
95 .map(|dir| (center + dir * 31).with_z(top_floor - 1))
96 .collect::<Vec<_>>();
97 Self {
98 door_tile: door_tile_pos,
99 alt,
100 base,
101 center,
102 size,
103 bldg_height,
104 diameter,
105 docking_positions,
106 }
107 }
108}
109
110impl Structure for CoastalAirshipDock {
111 #[cfg(feature = "dyn-lib")]
112 #[unsafe(export_name = "as_dyn_structure_coastalairshipdock")]
113 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
114 Some((
115 Self::as_dyn_impl(self),
116 "as_dyn_structure_coastalairshipdock",
117 ))
118 }
119
120 fn spawn_rules_inner(
121 &self,
122 spawn_rules: &mut SpawnRules,
123 _land: &Land,
124 wpos: Vec2<i32>,
125 _weight: f32,
126 ) {
127 const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
132 spawn_rules.trees &= !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2);
133
134 const MIN_FLAT_DIST: f32 = 12.0;
135 const SLOPE_LENGTH: f32 = 16.0;
136 let dist = wpos.as_::<f32>().distance(self.center.as_());
137 let weight = (1.0 - (dist - MIN_FLAT_DIST).max(0.0) / SLOPE_LENGTH).max(0.0);
138 spawn_rules.prefer_alt(self.alt as f32, weight);
139 }
140
141 fn airship_dock_info(&self) -> Option<AirshipDockInfo<'_>> {
142 Some(AirshipDockInfo {
143 door_tile: self.door_tile,
144 center: self.center,
145 docking_positions: &self.docking_positions,
146 })
147 }
148
149 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
150 let base = self.base;
151 let center = self.center;
152 let white = Fill::Sampling(Arc::new(|center| {
153 Some(match (RandomField::new(0).get(center)) % 37 {
154 0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
155 9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
156 18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
157 27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
158 _ => Block::new(BlockKind::Rock, Rgb::new(255, 244, 193)),
159 })
160 }));
161 let blue_broken = Fill::Sampling(Arc::new(|center| {
162 Some(match (RandomField::new(0).get(center)) % 20 {
163 0 => Block::new(BlockKind::Rock, Rgb::new(30, 187, 235)),
164 _ => Block::new(BlockKind::Rock, Rgb::new(11, 146, 187)),
165 })
166 }));
167
168 let length = self.diameter / 2;
169 let width = (self.diameter / 2) - 1;
170 let height = 15;
171 painter
173 .aabb(Aabb {
174 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - 2),
175 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 1),
176 })
177 .fill(blue_broken.clone());
178
179 for dir in CARDINALS {
180 let frame_pos = Vec2::new(
181 center.x + dir.x * (length + 5),
182 center.y + dir.y * (width + 5),
183 );
184 painter
185 .line(center.with_z(base - 1), frame_pos.with_z(base - 1), 3.0)
186 .fill(blue_broken.clone());
187 }
188 painter
190 .aabb(Aabb {
191 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - height),
192 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 2),
193 })
194 .fill(white.clone());
195 for f in 0..8 {
196 painter
197 .aabb(Aabb {
198 min: Vec2::new(center.x - length - 7 - f, center.y - width - 7 - f)
199 .with_z(base - 3 - f),
200 max: Vec2::new(center.x + length + 8 + f, center.y + width + 8 + f)
201 .with_z(base - 2 - f),
202 })
203 .fill(white.clone());
204 }
205 painter
207 .aabb(Aabb {
208 min: Vec2::new(center.x - length - 5, center.y - width - 5).with_z(base - 2),
209 max: Vec2::new(center.x + length + 6, center.y + width + 6).with_z(base + height),
210 })
211 .clear();
212 for dir in CARDINALS {
214 let clear_pos = Vec2::new(
215 center.x + dir.x * (length + 7),
216 center.y + dir.y * (width + 7),
217 );
218 painter
219 .line(center.with_z(base - 1), clear_pos.with_z(base - 1), 2.0)
220 .clear();
221 }
222
223 let size = self.size;
225 let room_offset = size / 6;
226 let bldg_height = self.bldg_height;
227 let tower_height = (bldg_height as f32 * 1.5).round() as i32;
228 for r in 0..=4 {
229 let bldg_size = size - (room_offset * r);
230 let bldg_base = base + ((bldg_height + 2) * r);
231 let level_height = bldg_base + bldg_height;
232 if r == 4 {
233 painter
235 .cylinder_with_radius(
236 center.with_z(level_height - 1),
237 (bldg_size + 5) as f32,
238 1.0,
239 )
240 .fill(white.clone());
241 painter
243 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 1.0)
244 .fill(blue_broken.clone());
245
246 let agent_booth_mask = painter.aabb(Aabb {
250 min: (Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size - 4)))
251 .with_z(level_height),
252 max: (Vec2::new(center.x - (bldg_size + 5), center.y + (bldg_size + 5)))
253 .with_z(level_height + 2),
254 });
255 painter
256 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 2.0)
257 .intersect(agent_booth_mask)
258 .fill(blue_broken.clone());
259
260 painter
262 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 4) as f32, 2.0)
263 .clear();
264
265 painter
267 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 2) as f32, 1.0)
268 .intersect(agent_booth_mask)
269 .fill(blue_broken.clone());
270 painter
272 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 1) as f32, 1.0)
273 .intersect(agent_booth_mask)
274 .clear();
275 painter
277 .line(
278 Vec2::new(center.x - (bldg_size + 3), center.y + (bldg_size - 5))
279 .with_z(level_height),
280 Vec2::new(center.x - (bldg_size + 2), center.y + (bldg_size - 5))
281 .with_z(level_height),
282 0.5,
283 )
284 .fill(blue_broken.clone());
285 painter
286 .line(
287 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 1))
288 .with_z(level_height),
289 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 2))
290 .with_z(level_height),
291 0.5,
292 )
293 .fill(blue_broken.clone());
294
295 painter
297 .aabb(Aabb {
298 min: Vec2::new(center.x - 3, center.y + bldg_size * 2).with_z(level_height),
299 max: Vec2::new(center.x + 3, center.y - (bldg_size * 2))
300 .with_z(level_height + 1),
301 })
302 .clear();
303 painter
304 .aabb(Aabb {
305 min: Vec2::new(center.x - bldg_size * 2, center.y - 3).with_z(level_height),
306 max: Vec2::new(center.x + bldg_size * 2, center.y + 3)
307 .with_z(level_height + 1),
308 })
309 .clear();
310
311 painter
313 .cylinder_with_radius(center.with_z(level_height), 1.0, tower_height as f32)
314 .fill(white.clone());
315 painter
316 .cone_with_radius(center.with_z(level_height + tower_height), 4.0, 3.0)
317 .fill(white.clone());
318
319 let glowing =
320 Fill::Block(Block::new(BlockKind::GlowingRock, Rgb::new(30, 187, 235)));
321 painter
322 .sphere(Aabb {
323 min: (center - 4).with_z(level_height + tower_height + 3),
324 max: (center + 4).with_z(level_height + tower_height + 11),
325 })
326 .fill(glowing.clone());
327
328 let cargo_pos = Vec2::new(center.x, center.y + 5);
330 for dir in CARDINALS.iter() {
331 let sprite_pos = cargo_pos + dir;
332 let rows = 1 + (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
333 for r in 0..rows {
334 painter
335 .aabb(Aabb {
336 min: (sprite_pos).with_z(level_height + r),
337 max: (sprite_pos + 1).with_z(level_height + 1 + r),
338 })
339 .fill(Fill::Block(Block::air(
340 match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2)
341 as i32
342 {
343 0 => SpriteKind::Barrel,
344 _ => SpriteKind::CrateBlock,
345 },
346 )));
347 if r > 1 {
348 painter.owned_resource_sprite(
349 sprite_pos.with_z(level_height + 1 + r),
350 SpriteKind::Crate,
351 0,
352 );
353 }
354 }
355
356 let dock_pos = center + dir * 27;
358 let rotation = -f32::atan2(dir.x as f32, dir.y as f32);
359
360 painter
361 .aabb(Aabb {
362 min: Vec2::new(center.x - 4, center.y + bldg_size + 1)
363 .with_z(level_height - 1),
364 max: Vec2::new(center.x + 4, center.y + 27).with_z(level_height),
365 })
366 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
367 .fill(white.clone());
368 painter
369 .cylinder_with_radius(dock_pos.with_z(level_height), 5.0, 1.0)
370 .fill(blue_broken.clone());
371 painter
372 .cylinder_with_radius(dock_pos.with_z(level_height - 1), 4.0, 2.0)
373 .fill(white.clone());
374
375 painter
377 .line(
378 Vec2::new(center.x - 4, center.y + 2)
379 .with_z(level_height + tower_height),
380 Vec2::new(center.x - 4, center.y + tower_height + 2)
381 .with_z(level_height),
382 0.75,
383 )
384 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
385 .fill(white.clone());
386 painter
387 .line(
388 Vec2::new(center.x + 3, center.y + 2)
389 .with_z(level_height + tower_height),
390 Vec2::new(center.x + 3, center.y + tower_height + 2)
391 .with_z(level_height),
392 0.75,
393 )
394 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
395 .fill(white.clone());
396 }
397 let campfire_pos = (center + Vec2::new(0, -3)).with_z(level_height);
399 painter.spawn(
400 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
401 .into_special(SpecialEntity::Waypoint),
402 );
403 }
404 painter
405 .cylinder(Aabb {
406 min: (center - bldg_size).with_z(bldg_base - 2),
407 max: (center + bldg_size).with_z(level_height),
408 })
409 .fill(white.clone());
410 }
411 for r in 0..=4 {
412 let bldg_size = size - (room_offset * r);
413 let bldg_base = base + ((bldg_height + 2) * r);
414
415 let step_positions = place_circular(center, (bldg_size - 1) as f32, 14);
416 for (s, step_pos) in step_positions.enumerate() {
417 let step_size = (size / 3) - r;
418
419 painter
420 .cylinder(Aabb {
421 min: (step_pos - step_size).with_z(bldg_base - 2 + s as i32),
422 max: (step_pos + step_size).with_z(bldg_base + 4 + s as i32),
423 })
424 .clear();
425 painter
426 .cylinder(Aabb {
427 min: (step_pos - step_size).with_z(bldg_base - 3 + s as i32),
428 max: (step_pos + step_size).with_z(bldg_base - 2 + s as i32),
429 })
430 .fill(blue_broken.clone());
431 painter
432 .cylinder(Aabb {
433 min: (step_pos - step_size + 1).with_z(bldg_base - 4 + s as i32),
434 max: (step_pos + step_size - 1).with_z(bldg_base - 2 + s as i32),
435 })
436 .fill(white.clone());
437 }
438 let lamp_positions = place_circular(center, (bldg_size + 1) as f32, 14);
439 for (l, lamp_pos) in lamp_positions.enumerate() {
440 if (RandomField::new(0).get(lamp_pos.with_z(base)) % 4) < 1 {
441 painter
442 .aabb(Aabb {
443 min: (lamp_pos - 1).with_z(bldg_base - 3 + l as i32),
444 max: (lamp_pos + 1).with_z(bldg_base - 2 + l as i32),
445 })
446 .fill(blue_broken.clone());
447
448 painter.sprite(
449 lamp_pos.with_z(bldg_base - 2 + l as i32),
450 SpriteKind::FireBowlGround,
451 );
452 }
453 }
454 }
455 }
456}