Skip to main content

veloren_world/site/plot/
myrmidon_house.rs

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
12/// Represents house data generated by the `generate()` method
13pub struct MyrmidonHouse {
14    /// Axis aligned bounding region for the house
15    bounds: Aabr<i32>,
16    /// Approximate altitude of the door tile
17    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            // circle
91            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            // foundation
155            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            // fence
162            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            // house
189            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            // roof
201
202            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            // pillars
303            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        // npcs
356        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                // catacomb
370                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                            // storey exit clear
402                            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}