veloren_world/site2/plot/
myrmidon_house.rs

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