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 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
110 SpawnRules {
111 trees: {
112 const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
117 !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
118 },
119 waypoints: false,
120 ..SpawnRules::default()
121 }
122 }
123}
124
125impl Structure for CoastalAirshipDock {
126 #[cfg(feature = "use-dyn-lib")]
127 const UPDATE_FN: &'static [u8] = b"render_coastal_airship_dock\0";
128
129 #[cfg_attr(
130 feature = "be-dyn-lib",
131 unsafe(export_name = "render_coastal_airship_dock")
132 )]
133 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
134 let base = self.base;
135 let center = self.center;
136 let white = Fill::Sampling(Arc::new(|center| {
137 Some(match (RandomField::new(0).get(center)) % 37 {
138 0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
139 9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
140 18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
141 27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
142 _ => Block::new(BlockKind::Rock, Rgb::new(255, 244, 193)),
143 })
144 }));
145 let blue_broken = Fill::Sampling(Arc::new(|center| {
146 Some(match (RandomField::new(0).get(center)) % 20 {
147 0 => Block::new(BlockKind::Rock, Rgb::new(30, 187, 235)),
148 _ => Block::new(BlockKind::Rock, Rgb::new(11, 146, 187)),
149 })
150 }));
151
152 let length = self.diameter / 2;
153 let width = (self.diameter / 2) - 1;
154 let height = 15;
155 painter
157 .aabb(Aabb {
158 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - 2),
159 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 1),
160 })
161 .fill(blue_broken.clone());
162
163 for dir in CARDINALS {
164 let frame_pos = Vec2::new(
165 center.x + dir.x * (length + 5),
166 center.y + dir.y * (width + 5),
167 );
168 painter
169 .line(center.with_z(base - 1), frame_pos.with_z(base - 1), 3.0)
170 .fill(blue_broken.clone());
171 }
172 painter
174 .aabb(Aabb {
175 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - height),
176 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 2),
177 })
178 .fill(white.clone());
179 for f in 0..8 {
180 painter
181 .aabb(Aabb {
182 min: Vec2::new(center.x - length - 7 - f, center.y - width - 7 - f)
183 .with_z(base - 3 - f),
184 max: Vec2::new(center.x + length + 8 + f, center.y + width + 8 + f)
185 .with_z(base - 2 - f),
186 })
187 .fill(white.clone());
188 }
189 painter
191 .aabb(Aabb {
192 min: Vec2::new(center.x - length - 5, center.y - width - 5).with_z(base - 2),
193 max: Vec2::new(center.x + length + 6, center.y + width + 6).with_z(base + height),
194 })
195 .clear();
196 for dir in CARDINALS {
198 let clear_pos = Vec2::new(
199 center.x + dir.x * (length + 7),
200 center.y + dir.y * (width + 7),
201 );
202 painter
203 .line(center.with_z(base - 1), clear_pos.with_z(base - 1), 2.0)
204 .clear();
205 }
206
207 let size = self.size;
209 let room_offset = size / 6;
210 let bldg_height = self.bldg_height;
211 let tower_height = (bldg_height as f32 * 1.5).round() as i32;
212 for r in 0..=4 {
213 let bldg_size = size - (room_offset * r);
214 let bldg_base = base + ((bldg_height + 2) * r);
215 let level_height = bldg_base + bldg_height;
216 if r == 4 {
217 painter
219 .cylinder_with_radius(
220 center.with_z(level_height - 1),
221 (bldg_size + 5) as f32,
222 1.0,
223 )
224 .fill(white.clone());
225 painter
227 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 1.0)
228 .fill(blue_broken.clone());
229
230 let agent_booth_mask = painter.aabb(Aabb {
234 min: (Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size - 4)))
235 .with_z(level_height),
236 max: (Vec2::new(center.x - (bldg_size + 5), center.y + (bldg_size + 5)))
237 .with_z(level_height + 2),
238 });
239 painter
240 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 2.0)
241 .intersect(agent_booth_mask)
242 .fill(blue_broken.clone());
243
244 painter
246 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 4) as f32, 2.0)
247 .clear();
248
249 painter
251 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 2) as f32, 1.0)
252 .intersect(agent_booth_mask)
253 .fill(blue_broken.clone());
254 painter
256 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 1) as f32, 1.0)
257 .intersect(agent_booth_mask)
258 .clear();
259 painter
261 .line(
262 Vec2::new(center.x - (bldg_size + 3), center.y + (bldg_size - 5))
263 .with_z(level_height),
264 Vec2::new(center.x - (bldg_size + 2), center.y + (bldg_size - 5))
265 .with_z(level_height),
266 0.5,
267 )
268 .fill(blue_broken.clone());
269 painter
270 .line(
271 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 1))
272 .with_z(level_height),
273 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 2))
274 .with_z(level_height),
275 0.5,
276 )
277 .fill(blue_broken.clone());
278
279 painter
281 .aabb(Aabb {
282 min: Vec2::new(center.x - 3, center.y + bldg_size * 2).with_z(level_height),
283 max: Vec2::new(center.x + 3, center.y - (bldg_size * 2))
284 .with_z(level_height + 1),
285 })
286 .clear();
287 painter
288 .aabb(Aabb {
289 min: Vec2::new(center.x - bldg_size * 2, center.y - 3).with_z(level_height),
290 max: Vec2::new(center.x + bldg_size * 2, center.y + 3)
291 .with_z(level_height + 1),
292 })
293 .clear();
294
295 painter
297 .cylinder_with_radius(center.with_z(level_height), 1.0, tower_height as f32)
298 .fill(white.clone());
299 painter
300 .cone_with_radius(center.with_z(level_height + tower_height), 4.0, 3.0)
301 .fill(white.clone());
302
303 let glowing =
304 Fill::Block(Block::new(BlockKind::GlowingRock, Rgb::new(30, 187, 235)));
305 painter
306 .sphere(Aabb {
307 min: (center - 4).with_z(level_height + tower_height + 3),
308 max: (center + 4).with_z(level_height + tower_height + 11),
309 })
310 .fill(glowing.clone());
311
312 let cargo_pos = Vec2::new(center.x, center.y + 5);
314 for dir in CARDINALS.iter() {
315 let sprite_pos = cargo_pos + dir;
316 let rows = 1 + (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
317 for r in 0..rows {
318 painter
319 .aabb(Aabb {
320 min: (sprite_pos).with_z(level_height + r),
321 max: (sprite_pos + 1).with_z(level_height + 1 + r),
322 })
323 .fill(Fill::Block(Block::air(
324 match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2)
325 as i32
326 {
327 0 => SpriteKind::Barrel,
328 _ => SpriteKind::CrateBlock,
329 },
330 )));
331 if r > 1 {
332 painter.owned_resource_sprite(
333 sprite_pos.with_z(level_height + 1 + r),
334 SpriteKind::Crate,
335 0,
336 );
337 }
338 }
339
340 let dock_pos = center + dir * 27;
342 let rotation = -f32::atan2(dir.x as f32, dir.y as f32);
343
344 painter
345 .aabb(Aabb {
346 min: Vec2::new(center.x - 4, center.y + bldg_size + 1)
347 .with_z(level_height - 1),
348 max: Vec2::new(center.x + 4, center.y + 27).with_z(level_height),
349 })
350 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
351 .fill(white.clone());
352 painter
353 .cylinder_with_radius(dock_pos.with_z(level_height), 5.0, 1.0)
354 .fill(blue_broken.clone());
355 painter
356 .cylinder_with_radius(dock_pos.with_z(level_height - 1), 4.0, 2.0)
357 .fill(white.clone());
358
359 painter
361 .line(
362 Vec2::new(center.x - 4, center.y + 2)
363 .with_z(level_height + tower_height),
364 Vec2::new(center.x - 4, center.y + tower_height + 2)
365 .with_z(level_height),
366 0.75,
367 )
368 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
369 .fill(white.clone());
370 painter
371 .line(
372 Vec2::new(center.x + 3, center.y + 2)
373 .with_z(level_height + tower_height),
374 Vec2::new(center.x + 3, center.y + tower_height + 2)
375 .with_z(level_height),
376 0.75,
377 )
378 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
379 .fill(white.clone());
380 }
381 let campfire_pos = (center + Vec2::new(0, -3)).with_z(level_height);
383 painter.spawn(
384 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
385 .into_special(SpecialEntity::Waypoint),
386 );
387 }
388 painter
389 .cylinder(Aabb {
390 min: (center - bldg_size).with_z(bldg_base - 2),
391 max: (center + bldg_size).with_z(level_height),
392 })
393 .fill(white.clone());
394 }
395 for r in 0..=4 {
396 let bldg_size = size - (room_offset * r);
397 let bldg_base = base + ((bldg_height + 2) * r);
398
399 let step_positions = place_circular(center, (bldg_size - 1) as f32, 14);
400 for (s, step_pos) in step_positions.enumerate() {
401 let step_size = (size / 3) - r;
402
403 painter
404 .cylinder(Aabb {
405 min: (step_pos - step_size).with_z(bldg_base - 2 + s as i32),
406 max: (step_pos + step_size).with_z(bldg_base + 4 + s as i32),
407 })
408 .clear();
409 painter
410 .cylinder(Aabb {
411 min: (step_pos - step_size).with_z(bldg_base - 3 + s as i32),
412 max: (step_pos + step_size).with_z(bldg_base - 2 + s as i32),
413 })
414 .fill(blue_broken.clone());
415 painter
416 .cylinder(Aabb {
417 min: (step_pos - step_size + 1).with_z(bldg_base - 4 + s as i32),
418 max: (step_pos + step_size - 1).with_z(bldg_base - 2 + s as i32),
419 })
420 .fill(white.clone());
421 }
422 let lamp_positions = place_circular(center, (bldg_size + 1) as f32, 14);
423 for (l, lamp_pos) in lamp_positions.enumerate() {
424 if (RandomField::new(0).get(lamp_pos.with_z(base)) % 4) < 1 {
425 painter
426 .aabb(Aabb {
427 min: (lamp_pos - 1).with_z(bldg_base - 3 + l as i32),
428 max: (lamp_pos + 1).with_z(bldg_base - 2 + l as i32),
429 })
430 .fill(blue_broken.clone());
431
432 painter.sprite(
433 lamp_pos.with_z(bldg_base - 2 + l as i32),
434 SpriteKind::FireBowlGround,
435 );
436 }
437 }
438 }
439 }
440}