1use super::*;
2use crate::{
3 Land,
4 site::gen::{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 door_dir: Vec2<i32>,
36 tile_aabr: Aabr<i32>,
37 alt: Option<i32>,
38 ) -> Self {
39 let door_tile_pos = site.tile_center_wpos(door_tile);
40 let bounds = Aabr {
41 min: site.tile_wpos(tile_aabr.min),
42 max: site.tile_wpos(tile_aabr.max),
43 };
44 let diameter = (bounds.max.x - bounds.min.x).min(bounds.max.y - bounds.min.y);
45 let alt = alt.unwrap_or_else(|| {
46 land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32
47 }) + 2;
48 let size = 20;
49 let bldg_height = 12;
50 let base = alt + 1;
51 let center = bounds.center();
52 let top_floor = base + (bldg_height * 6) - 3;
53 let docking_positions = CARDINALS
54 .iter()
55 .map(|dir| (center + dir * 31).with_z(top_floor - 1))
56 .collect::<Vec<_>>();
57 Self {
58 door_tile: door_tile_pos,
59 alt,
60 base,
61 center,
62 size,
63 bldg_height,
64 diameter,
65 docking_positions,
66 }
67 }
68
69 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
70 SpawnRules {
71 trees: {
72 const AIRSHIP_MIN_TREE_DIST2: i32 = 89;
77 !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
78 },
79 waypoints: false,
80 ..SpawnRules::default()
81 }
82 }
83}
84
85impl Structure for CoastalAirshipDock {
86 #[cfg(feature = "use-dyn-lib")]
87 const UPDATE_FN: &'static [u8] = b"render_coastal_airship_dock\0";
88
89 #[cfg_attr(
90 feature = "be-dyn-lib",
91 unsafe(export_name = "render_coastal_airship_dock")
92 )]
93 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
94 let base = self.base;
95 let center = self.center;
96 let white = Fill::Sampling(Arc::new(|center| {
97 Some(match (RandomField::new(0).get(center)) % 37 {
98 0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
99 9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
100 18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
101 27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
102 _ => Block::new(BlockKind::Rock, Rgb::new(255, 244, 193)),
103 })
104 }));
105 let blue_broken = Fill::Sampling(Arc::new(|center| {
106 Some(match (RandomField::new(0).get(center)) % 20 {
107 0 => Block::new(BlockKind::Rock, Rgb::new(30, 187, 235)),
108 _ => Block::new(BlockKind::Rock, Rgb::new(11, 146, 187)),
109 })
110 }));
111
112 let length = self.diameter / 2;
113 let width = (self.diameter / 2) - 1;
114 let height = 15;
115 painter
117 .aabb(Aabb {
118 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - 2),
119 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 1),
120 })
121 .fill(blue_broken.clone());
122
123 for dir in CARDINALS {
124 let frame_pos = Vec2::new(
125 center.x + dir.x * (length + 5),
126 center.y + dir.y * (width + 5),
127 );
128 painter
129 .line(center.with_z(base - 1), frame_pos.with_z(base - 1), 3.0)
130 .fill(blue_broken.clone());
131 }
132 painter
134 .aabb(Aabb {
135 min: Vec2::new(center.x - length - 6, center.y - width - 6).with_z(base - height),
136 max: Vec2::new(center.x + length + 7, center.y + width + 7).with_z(base - 2),
137 })
138 .fill(white.clone());
139 for f in 0..8 {
140 painter
141 .aabb(Aabb {
142 min: Vec2::new(center.x - length - 7 - f, center.y - width - 7 - f)
143 .with_z(base - 3 - f),
144 max: Vec2::new(center.x + length + 8 + f, center.y + width + 8 + f)
145 .with_z(base - 2 - f),
146 })
147 .fill(white.clone());
148 }
149 painter
151 .aabb(Aabb {
152 min: Vec2::new(center.x - length - 5, center.y - width - 5).with_z(base - 2),
153 max: Vec2::new(center.x + length + 6, center.y + width + 6).with_z(base + height),
154 })
155 .clear();
156 for dir in CARDINALS {
158 let clear_pos = Vec2::new(
159 center.x + dir.x * (length + 7),
160 center.y + dir.y * (width + 7),
161 );
162 painter
163 .line(center.with_z(base - 1), clear_pos.with_z(base - 1), 2.0)
164 .clear();
165 }
166
167 let size = self.size;
169 let room_offset = size / 6;
170 let bldg_height = self.bldg_height;
171 let tower_height = (bldg_height as f32 * 1.5).round() as i32;
172 for r in 0..=4 {
173 let bldg_size = size - (room_offset * r);
174 let bldg_base = base + ((bldg_height + 2) * r);
175 let level_height = bldg_base + bldg_height;
176 if r == 4 {
177 painter
179 .cylinder_with_radius(
180 center.with_z(level_height - 1),
181 (bldg_size + 5) as f32,
182 1.0,
183 )
184 .fill(white.clone());
185 painter
187 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 1.0)
188 .fill(blue_broken.clone());
189
190 let agent_booth_mask = painter.aabb(Aabb {
194 min: (Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size - 4)))
195 .with_z(level_height),
196 max: (Vec2::new(center.x - (bldg_size + 5), center.y + (bldg_size + 5)))
197 .with_z(level_height + 2),
198 });
199 painter
200 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 5) as f32, 2.0)
201 .intersect(agent_booth_mask)
202 .fill(blue_broken.clone());
203
204 painter
206 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 4) as f32, 2.0)
207 .clear();
208
209 painter
211 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 2) as f32, 1.0)
212 .intersect(agent_booth_mask)
213 .fill(blue_broken.clone());
214 painter
216 .cylinder_with_radius(center.with_z(level_height), (bldg_size + 1) as f32, 1.0)
217 .intersect(agent_booth_mask)
218 .clear();
219 painter
221 .line(
222 Vec2::new(center.x - (bldg_size + 3), center.y + (bldg_size - 5))
223 .with_z(level_height),
224 Vec2::new(center.x - (bldg_size + 2), center.y + (bldg_size - 5))
225 .with_z(level_height),
226 0.5,
227 )
228 .fill(blue_broken.clone());
229 painter
230 .line(
231 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 1))
232 .with_z(level_height),
233 Vec2::new(center.x - (bldg_size - 4), center.y + (bldg_size + 2))
234 .with_z(level_height),
235 0.5,
236 )
237 .fill(blue_broken.clone());
238
239 painter
241 .aabb(Aabb {
242 min: Vec2::new(center.x - 3, center.y + bldg_size * 2).with_z(level_height),
243 max: Vec2::new(center.x + 3, center.y - (bldg_size * 2))
244 .with_z(level_height + 1),
245 })
246 .clear();
247 painter
248 .aabb(Aabb {
249 min: Vec2::new(center.x - bldg_size * 2, center.y - 3).with_z(level_height),
250 max: Vec2::new(center.x + bldg_size * 2, center.y + 3)
251 .with_z(level_height + 1),
252 })
253 .clear();
254
255 painter
257 .cylinder_with_radius(center.with_z(level_height), 1.0, tower_height as f32)
258 .fill(white.clone());
259 painter
260 .cone_with_radius(center.with_z(level_height + tower_height), 4.0, 3.0)
261 .fill(white.clone());
262
263 let glowing =
264 Fill::Block(Block::new(BlockKind::GlowingRock, Rgb::new(30, 187, 235)));
265 painter
266 .sphere(Aabb {
267 min: (center - 4).with_z(level_height + tower_height + 3),
268 max: (center + 4).with_z(level_height + tower_height + 11),
269 })
270 .fill(glowing.clone());
271
272 let cargo_pos = Vec2::new(center.x, center.y + 5);
274 for dir in CARDINALS.iter() {
275 let sprite_pos = cargo_pos + dir;
276 let rows = 1 + (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
277 for r in 0..rows {
278 painter
279 .aabb(Aabb {
280 min: (sprite_pos).with_z(level_height + r),
281 max: (sprite_pos + 1).with_z(level_height + 1 + r),
282 })
283 .fill(Fill::Block(Block::air(
284 match (RandomField::new(0).get(sprite_pos.with_z(base + r)) % 2)
285 as i32
286 {
287 0 => SpriteKind::Barrel,
288 _ => SpriteKind::CrateBlock,
289 },
290 )));
291 if r > 1 {
292 painter.owned_resource_sprite(
293 sprite_pos.with_z(level_height + 1 + r),
294 SpriteKind::Crate,
295 0,
296 );
297 }
298 }
299
300 let dock_pos = center + dir * 27;
302 let rotation = -f32::atan2(dir.x as f32, dir.y as f32);
303
304 painter
305 .aabb(Aabb {
306 min: Vec2::new(center.x - 4, center.y + bldg_size + 1)
307 .with_z(level_height - 1),
308 max: Vec2::new(center.x + 4, center.y + 27).with_z(level_height),
309 })
310 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
311 .fill(white.clone());
312 painter
313 .cylinder_with_radius(dock_pos.with_z(level_height), 5.0, 1.0)
314 .fill(blue_broken.clone());
315 painter
316 .cylinder_with_radius(dock_pos.with_z(level_height - 1), 4.0, 2.0)
317 .fill(white.clone());
318
319 painter
321 .line(
322 Vec2::new(center.x - 4, center.y + 2)
323 .with_z(level_height + tower_height),
324 Vec2::new(center.x - 4, center.y + tower_height + 2)
325 .with_z(level_height),
326 0.75,
327 )
328 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
329 .fill(white.clone());
330 painter
331 .line(
332 Vec2::new(center.x + 3, center.y + 2)
333 .with_z(level_height + tower_height),
334 Vec2::new(center.x + 3, center.y + tower_height + 2)
335 .with_z(level_height),
336 0.75,
337 )
338 .rotate_about(Mat3::rotation_z(rotation).as_(), center.with_z(base))
339 .fill(white.clone());
340 }
341 let campfire_pos = (center + Vec2::new(0, -3)).with_z(level_height);
343 painter.spawn(
344 EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
345 .into_special(SpecialEntity::Waypoint),
346 );
347 }
348 painter
349 .cylinder(Aabb {
350 min: (center - bldg_size).with_z(bldg_base - 2),
351 max: (center + bldg_size).with_z(level_height),
352 })
353 .fill(white.clone());
354 }
355 for r in 0..=4 {
356 let bldg_size = size - (room_offset * r);
357 let bldg_base = base + ((bldg_height + 2) * r);
358
359 let step_positions = place_circular(center, (bldg_size - 1) as f32, 14);
360 for (s, step_pos) in step_positions.enumerate() {
361 let step_size = (size / 3) - r;
362
363 painter
364 .cylinder(Aabb {
365 min: (step_pos - step_size).with_z(bldg_base - 2 + s as i32),
366 max: (step_pos + step_size).with_z(bldg_base + 4 + s as i32),
367 })
368 .clear();
369 painter
370 .cylinder(Aabb {
371 min: (step_pos - step_size).with_z(bldg_base - 3 + s as i32),
372 max: (step_pos + step_size).with_z(bldg_base - 2 + s as i32),
373 })
374 .fill(blue_broken.clone());
375 painter
376 .cylinder(Aabb {
377 min: (step_pos - step_size + 1).with_z(bldg_base - 4 + s as i32),
378 max: (step_pos + step_size - 1).with_z(bldg_base - 2 + s as i32),
379 })
380 .fill(white.clone());
381 }
382 let lamp_positions = place_circular(center, (bldg_size + 1) as f32, 14);
383 for (l, lamp_pos) in lamp_positions.enumerate() {
384 if (RandomField::new(0).get(lamp_pos.with_z(base)) % 4) < 1 {
385 painter
386 .aabb(Aabb {
387 min: (lamp_pos - 1).with_z(bldg_base - 3 + l as i32),
388 max: (lamp_pos + 1).with_z(bldg_base - 2 + l as i32),
389 })
390 .fill(blue_broken.clone());
391
392 painter.sprite(
393 lamp_pos.with_z(bldg_base - 2 + l as i32),
394 SpriteKind::FireBowlGround,
395 );
396 }
397 }
398 }
399 }
400}