1use super::*;
2use crate::{
3 Land,
4 site::generation::place_circular,
5 util::{CARDINALS, NEIGHBORS, RandomField, Sampler, within_distance},
6};
7use common::generation::EntityInfo;
8use rand::prelude::*;
9use std::sync::Arc;
10use vek::*;
11
12pub struct MyrmidonHouse {
14 bounds: Aabr<i32>,
16 pub(crate) alt: i32,
18}
19
20impl MyrmidonHouse {
21 pub fn generate(
22 land: &Land,
23 _rng: &mut impl Rng,
24 site: &Site,
25 tile_aabr: Aabr<i32>,
26 alt: Option<i32>,
27 ) -> Self {
28 let bounds = Aabr {
29 min: site.tile_wpos(tile_aabr.min),
30 max: site.tile_wpos(tile_aabr.max),
31 };
32 Self {
33 bounds,
34 alt: alt.unwrap_or_else(|| {
35 land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
36 as i32
37 + 2
38 }),
39 }
40 }
41}
42
43impl Structure for MyrmidonHouse {
44 #[cfg(feature = "dyn-lib")]
45 #[unsafe(export_name = "as_dyn_structure_myrmidonhouse")]
46 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
47 Some((Self::as_dyn_impl(self), "as_dyn_structure_myrmidonhouse"))
48 }
49
50 fn spawn_rules_inner(
51 &self,
52 spawn_rules: &mut SpawnRules,
53 _land: &Land,
54 wpos: Vec2<i32>,
55 _weight: f32,
56 ) {
57 spawn_rules.trees &= !within_distance(wpos, self.bounds.center(), 85);
58 spawn_rules.waypoints = false;
59 }
60
61 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
62 let base = self.alt + 3;
63 let center = self.bounds.center();
64 let mut rng = rand::rng();
65 let sandstone_unbroken = Fill::Sampling(Arc::new(|center| {
66 Some(match (RandomField::new(0).get(center)) % 37 {
67 0..=8 => Block::new(BlockKind::Rock, Rgb::new(245, 212, 129)),
68 9..=17 => Block::new(BlockKind::Rock, Rgb::new(246, 214, 133)),
69 18..=26 => Block::new(BlockKind::Rock, Rgb::new(247, 216, 136)),
70 27..=35 => Block::new(BlockKind::Rock, Rgb::new(248, 219, 142)),
71 _ => Block::new(BlockKind::Rock, Rgb::new(235, 178, 99)),
72 })
73 }));
74 let sandstone = Fill::Sampling(Arc::new(|center| {
75 Some(match (RandomField::new(0).get(center)) % 42 {
76 0..=8 => Block::new(BlockKind::Rock, Rgb::new(245, 212, 129)),
77 9..=17 => Block::new(BlockKind::Rock, Rgb::new(246, 214, 133)),
78 18..=26 => Block::new(BlockKind::Rock, Rgb::new(247, 216, 136)),
79 27..=35 => Block::new(BlockKind::Rock, Rgb::new(248, 219, 142)),
80 36..=37 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
81 _ => Block::new(BlockKind::Rock, Rgb::new(235, 178, 99)),
82 })
83 }));
84 let roof_color = Fill::Brick(BlockKind::Sand, Rgb::new(115, 32, 2), 12);
85 let diameter =
86 (self.bounds.max.x - self.bounds.min.x).min(self.bounds.max.y - self.bounds.min.y);
87 let bldg_var = RandomField::new(0).get(center.with_z(base + 5)) % 4;
88
89 if bldg_var < 1 {
90 let circle_radius = 2 * (diameter / 5);
92 painter
93 .cylinder(Aabb {
94 min: (center - circle_radius).with_z(base - 30),
95 max: (center + circle_radius).with_z(base - 1),
96 })
97 .fill(sandstone_unbroken.clone());
98 painter
99 .cylinder(Aabb {
100 min: (center - circle_radius + 1).with_z(base - 1),
101 max: (center + circle_radius - 1).with_z(base),
102 })
103 .fill(sandstone.clone());
104 painter
105 .cylinder(Aabb {
106 min: (center - circle_radius + 2).with_z(base - 1),
107 max: (center + circle_radius - 2).with_z(base + 15),
108 })
109 .clear();
110
111 for dir in CARDINALS {
112 let clear_pos = center + dir * circle_radius;
113 let clear_rand = RandomField::new(0).get(clear_pos.with_z(base)) % 2;
114 if clear_rand < 1 {
115 painter
116 .cylinder(Aabb {
117 min: (clear_pos - 6).with_z(base - 1),
118 max: (clear_pos + 6).with_z(base),
119 })
120 .clear();
121 }
122 }
123 let pillars = 8 + (RandomField::new(0).get(center.with_z(base + 2)) % 6) as i32;
124 let pillar_positions = place_circular(center, (circle_radius - 5) as f32, pillars);
125 for pillar in pillar_positions {
126 let pillar_rand = RandomField::new(0).get(pillar.with_z(base)) % 5;
127 if pillar_rand > 0 {
128 let pillar_heigth = 10 + pillar_rand as i32;
129 painter
130 .cylinder(Aabb {
131 min: (pillar - 3).with_z(base - 1),
132 max: (pillar + 3).with_z(base),
133 })
134 .fill(sandstone.clone());
135 painter
136 .cylinder(Aabb {
137 min: (pillar - 2).with_z(base),
138 max: (pillar + 2).with_z(base + pillar_heigth),
139 })
140 .fill(sandstone.clone());
141 for dir in CARDINALS {
142 let clear_pos = pillar + dir * 3;
143 let clear_var = RandomField::new(0).get(clear_pos.with_z(base)) % 2;
144
145 if clear_var > 0 {
146 painter
147 .sphere_with_radius(clear_pos.with_z(base + pillar_heigth), 3.0)
148 .clear();
149 }
150 }
151 }
152 }
153 } else {
154 painter
156 .aabb(Aabb {
157 min: (center - (diameter / 2)).with_z(base - 30),
158 max: (center + (diameter / 2)).with_z(base - 2),
159 })
160 .fill(sandstone_unbroken.clone());
161 painter
163 .aabb(Aabb {
164 min: Vec2::new(center.x - (diameter / 2), center.y - (diameter / 2))
165 .with_z(base - 2),
166 max: Vec2::new(center.x + (diameter / 2), center.y + (diameter / 2))
167 .with_z(base - 1),
168 })
169 .fill(sandstone.clone());
170 painter
171 .aabb(Aabb {
172 min: Vec2::new(center.x - (diameter / 2) + 1, center.y - (diameter / 2) + 1)
173 .with_z(base - 2),
174 max: Vec2::new(center.x + (diameter / 2) - 1, center.y + (diameter / 2) - 1)
175 .with_z(base + 15),
176 })
177 .clear();
178 for dir in CARDINALS {
179 let gate_pos = center + dir * (diameter / 2);
180 painter
181 .cylinder(Aabb {
182 min: (gate_pos - 6).with_z(base - 2),
183 max: (gate_pos + 6).with_z(base + 6),
184 })
185 .clear();
186 }
187
188 let rand = RandomField::new(0).get(center.with_z(base)) % 2;
190 let heigth_rand = (RandomField::new(0).get(center.with_z(base)) % 10) as i32;
191 let bldg_length = (diameter / 2) - 5;
192 let bldg_width = diameter / 3;
193 let bldg_height = (diameter / 4) + heigth_rand;
194 let (x_axis, y_axis) = if rand > 0 {
195 (bldg_length, bldg_width)
196 } else {
197 (bldg_width, bldg_length)
198 };
199
200 painter
203 .aabb(Aabb {
204 min: Vec2::new(center.x - x_axis, center.y - y_axis).with_z(base + bldg_height),
205 max: Vec2::new(center.x + x_axis, center.y + y_axis)
206 .with_z(base + bldg_height + 1),
207 })
208 .fill(sandstone.clone());
209 painter
210 .aabb(Aabb {
211 min: Vec2::new(center.x - x_axis + 1, center.y - y_axis + 1)
212 .with_z(base + bldg_height + 1),
213 max: Vec2::new(center.x + x_axis - 1, center.y + y_axis - 1)
214 .with_z(base + bldg_height + 2),
215 })
216 .fill(sandstone.clone());
217 painter
218 .aabb(Aabb {
219 min: Vec2::new(center.x - x_axis, center.y - y_axis)
220 .with_z(base + bldg_height + 2),
221 max: Vec2::new(center.x + x_axis, center.y + y_axis)
222 .with_z(base + bldg_height + 3),
223 })
224 .fill(sandstone.clone());
225 painter
226 .aabb(Aabb {
227 min: Vec2::new(center.x - x_axis + 1, center.y - y_axis + 1)
228 .with_z(base + bldg_height + 3),
229 max: Vec2::new(center.x + x_axis - 1, center.y + y_axis - 1)
230 .with_z(base + bldg_height + 4),
231 })
232 .fill(sandstone.clone());
233 let roof_dir = if rand > 0 { Dir2::X } else { Dir2::Y };
234 painter
235 .gable(
236 Aabb {
237 min: Vec2::new(center.x - x_axis, center.y - y_axis)
238 .with_z(base + bldg_height + 4),
239 max: Vec2::new(center.x + x_axis, center.y + y_axis)
240 .with_z(base + bldg_height + 8),
241 },
242 4 * (x_axis / 5),
243 roof_dir,
244 )
245 .fill(sandstone.clone());
246
247 painter
248 .gable(
249 Aabb {
250 min: Vec2::new(center.x - x_axis + 1, center.y - y_axis + 2)
251 .with_z(base + bldg_height + 5),
252 max: Vec2::new(center.x + x_axis - 1, center.y + y_axis - 2)
253 .with_z(base + bldg_height + 9),
254 },
255 4 * (x_axis / 5),
256 roof_dir,
257 )
258 .fill(roof_color.clone());
259 if rand > 0 {
260 for r in 0..((x_axis / 2) - 1) {
261 painter
262 .gable(
263 Aabb {
264 min: Vec2::new(
265 center.x - x_axis + 4 + (4 * r),
266 center.y - y_axis - 1,
267 )
268 .with_z(base + bldg_height + 6),
269 max: Vec2::new(
270 center.x - x_axis + 5 + (4 * r),
271 center.y + y_axis + 1,
272 )
273 .with_z(base + bldg_height + 10),
274 },
275 4 * (x_axis / 5),
276 roof_dir,
277 )
278 .fill(roof_color.clone());
279 }
280 } else {
281 for r in 0..((y_axis / 2) - 1) {
282 painter
283 .gable(
284 Aabb {
285 min: Vec2::new(
286 center.x - x_axis - 1,
287 center.y - y_axis + 4 + (4 * r),
288 )
289 .with_z(base + bldg_height + 6),
290 max: Vec2::new(
291 center.x + x_axis + 1,
292 center.y - y_axis + 5 + (4 * r),
293 )
294 .with_z(base + bldg_height + 10),
295 },
296 4 * (x_axis / 5),
297 roof_dir,
298 )
299 .fill(roof_color.clone());
300 }
301 }
302 let pillar_x_axis = 3 * (x_axis / 4);
304 let pillar_y_axis = 3 * (y_axis / 4);
305 for dir in NEIGHBORS {
306 let pillar_pos = Vec2::new(
307 center.x + dir.x * pillar_x_axis,
308 center.y + dir.y * pillar_y_axis,
309 );
310
311 painter
312 .cylinder(Aabb {
313 min: (pillar_pos - 3).with_z(base - 2),
314 max: (pillar_pos + 3).with_z(base - 1),
315 })
316 .fill(sandstone.clone());
317 painter
318 .cylinder(Aabb {
319 min: (pillar_pos - 2).with_z(base - 1),
320 max: (pillar_pos + 2).with_z(base + bldg_height - 2),
321 })
322 .fill(sandstone.clone());
323 for p in 0..3 {
324 painter
325 .cylinder(Aabb {
326 min: (pillar_pos - 3 - p).with_z(base + bldg_height - 3 + p),
327 max: (pillar_pos + 3 + p).with_z(base + bldg_height - 2 + p),
328 })
329 .fill(sandstone.clone());
330 }
331
332 let decay = (RandomField::new(0).get(pillar_pos.with_z(base)) % 6) as i32;
333
334 if decay < 1 {
335 let decay_rand =
336 12.0 + (RandomField::new(0).get(pillar_pos.with_z(base)) % 6) as f32;
337
338 painter
339 .sphere_with_radius(pillar_pos.with_z(base + bldg_height + 8), decay_rand)
340 .clear();
341 for dir in CARDINALS {
342 let clear_pos = pillar_pos + dir * 3;
343 let clear_var = RandomField::new(0).get(clear_pos.with_z(base)) % 2;
344
345 if clear_var > 0 {
346 painter
347 .sphere_with_radius(clear_pos.with_z(base + bldg_height - 4), 3.0)
348 .clear();
349 }
350 }
351 }
352 }
353 }
354
355 let amount = (RandomField::new(0).get(center.with_z(base + 3)) % 5) as i32;
357 for n in 0..amount {
358 let entities = [
359 "common.entity.dungeon.myrmidon.hoplite",
360 "common.entity.dungeon.myrmidon.marksman",
361 "common.entity.dungeon.myrmidon.strategian",
362 ];
363 let npc_pos = (center + n).with_z(base);
364 let npc = entities[(RandomField::new(0).get(npc_pos) % entities.len() as u32) as usize];
365 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(npc, &mut rng, None));
366 }
367 if amount < 1 {
368 if bldg_var > 0 {
369 painter
371 .aabb(Aabb {
372 min: (center - (diameter / 2) + 1).with_z(base - 29),
373 max: (center + (diameter / 2) - 1).with_z(base - 3),
374 })
375 .fill(sandstone.clone());
376
377 for dir in NEIGHBORS {
378 for r in 1..=3 {
379 let room_pos = center + dir * ((diameter / 7) * r);
380 for s in 0..3 {
381 let room_var =
382 RandomField::new(0).get(room_pos.with_z(base + r + s)) % 6;
383 let room_dir = if room_var < 3 { Dir2::Y } else { Dir2::X };
384
385 let room = painter.vault(
386 Aabb {
387 min: (room_pos - (diameter / 10)).with_z(base - 29 + (8 * s)),
388 max: (room_pos + (diameter / 10)).with_z(base - 23 + (8 * s)),
389 },
390 room_dir,
391 );
392
393 let chest_var =
394 RandomField::new(0).get(room_pos.with_z(base + r + s)) % 10;
395
396 match room_var {
397 0 => room.fill(sandstone.clone()),
398 _ => room.clear(),
399 };
400
401 if room_var > 3 {
403 let carve_heigth = if s < 2 { 12 } else { 8 };
404 painter
405 .vault(
406 Aabb {
407 min: (room_pos - (diameter / 10))
408 .with_z(base - 29 + (8 * s)),
409 max: (room_pos + (diameter / 10))
410 .with_z(base - 29 + carve_heigth + (8 * s)),
411 },
412 room_dir,
413 )
414 .clear();
415 }
416 if s < 1 && room_var > 1 && chest_var > 8 {
417 painter
418 .sprite(room_pos.with_z(base - 29), SpriteKind::DungeonChest4);
419 }
420 }
421 }
422 }
423 painter
424 .aabb(Aabb {
425 min: (center - (diameter / 10)).with_z(base - 6),
426 max: (center + (diameter / 10)).with_z(base + 2),
427 })
428 .clear();
429 }
430 let npc_pos = (center - 8).with_z(base);
431
432 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
433 "common.entity.dungeon.myrmidon.cyclops",
434 &mut rng,
435 None,
436 ));
437 }
438 }
439}