Skip to main content

veloren_world/site/plot/
jungle_ruin.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site::generation::PrimitiveTransform,
6    util::{RandomField, sampler::Sampler},
7};
8use common::{
9    generation::EntityInfo,
10    terrain::{SpriteKind, Structure as PrefabStructure, StructuresGroup, sprite::SpriteCfg},
11};
12use lazy_static::lazy_static;
13use rand::prelude::*;
14use std::{f32::consts::TAU, sync::Arc};
15use vek::*;
16
17pub struct JungleRuin {
18    bounds: Aabr<i32>,
19    pub(crate) alt: i32,
20}
21impl JungleRuin {
22    pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
23        let bounds = Aabr {
24            min: site.tile_wpos(tile_aabr.min),
25            max: site.tile_wpos(tile_aabr.max),
26        };
27        Self {
28            bounds,
29            alt: land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
30                as i32
31                + 2,
32        }
33    }
34}
35
36impl Structure for JungleRuin {
37    #[cfg(feature = "dyn-lib")]
38    #[unsafe(export_name = "as_dyn_structure_junglerun")]
39    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
40        Some((Self::as_dyn_impl(self), "as_dyn_structure_junglerun"))
41    }
42
43    fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
44        let center = self.bounds.center();
45        let plot_base = land.get_alt_approx(center) as i32;
46        let mut rng = rand::rng();
47        let stone = Fill::Sampling(stone_color(BlockKind::Rock));
48        let weak_stone = Fill::Sampling(stone_color(BlockKind::WeakRock));
49        let stone_broken = Fill::Sampling(Arc::new(|center| {
50            Some(match (RandomField::new(0).get(center)) % 56 {
51                0..=8 => Block::new(BlockKind::Rock, Rgb::new(92, 99, 86)),
52                9..=17 => Block::new(BlockKind::Rock, Rgb::new(83, 89, 78)),
53                18..=26 => Block::new(BlockKind::Rock, Rgb::new(75, 89, 66)),
54                27..=35 => Block::new(BlockKind::Rock, Rgb::new(79, 83, 73)),
55                36..=44 => Block::new(BlockKind::Rock, Rgb::new(66, 80, 59)),
56                45..=49 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
57                _ => Block::new(BlockKind::Rock, Rgb::new(88, 94, 83)),
58            })
59        }));
60        let grass_fill = Fill::Sampling(Arc::new(|wpos| {
61            Some(match (RandomField::new(0).get(wpos)) % 30 {
62                1..=2 => Block::air(SpriteKind::ShortGrass),
63                3..=7 => Block::air(SpriteKind::LongGrass),
64                8 => Block::air(SpriteKind::JungleFern),
65                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
66            })
67        }));
68        let mut ruin_positions = vec![];
69        let pos_var = RandomField::new(0).get(center.with_z(plot_base)) % 10;
70        let radius = 25 + pos_var;
71        let ruins = 12.0 + pos_var as f32;
72        let phi = TAU / ruins;
73        for n in 1..=ruins as i32 {
74            let pos = Vec2::new(
75                center.x + (radius as f32 * ((n as f32 * phi).cos())) as i32,
76                center.y + (radius as f32 * ((n as f32 * phi).sin())) as i32,
77            );
78            let base = land.get_alt_approx(pos) as i32;
79            let ground_sink = RandomField::new(0).get(pos.with_z(base)) as i32 % 4;
80            let ruin_pos = pos.with_z(base - 8 - ground_sink);
81            ruin_positions.push(ruin_pos);
82        }
83        let underground_chamber = pos_var < 7;
84        // center ruin and underground chest chamber
85        let room_size = 10;
86        let height_handle = 10;
87        if underground_chamber {
88            // room
89            painter
90                .aabb(Aabb {
91                    min: (center - room_size - 1).with_z(plot_base - height_handle - room_size - 1),
92                    max: (center + room_size + 1).with_z(plot_base - height_handle + room_size + 1),
93                })
94                .fill(stone.clone());
95            painter
96                .aabb(Aabb {
97                    min: (center - room_size).with_z(plot_base - height_handle - room_size),
98                    max: (center + room_size).with_z(plot_base - height_handle + room_size + 2),
99                })
100                .fill(stone_broken.clone());
101            // platform
102            painter
103                .aabb(Aabb {
104                    min: (center - room_size + 1).with_z(plot_base - height_handle + room_size + 1),
105                    max: (center + room_size - 1).with_z(plot_base - height_handle + room_size + 2),
106                })
107                .clear();
108            let center_ruin_pos = center.with_z(plot_base - 1);
109            ruin_positions.push(center_ruin_pos);
110
111            // room decor
112            for d in 0..=5 {
113                painter
114                    .line(
115                        Vec2::new(center.x, center.y - room_size + 1)
116                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
117                        Vec2::new(center.x, center.y + room_size - 1)
118                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
119                        (room_size - (2 * d)) as f32,
120                    )
121                    .fill(stone_broken.clone());
122                painter
123                    .line(
124                        Vec2::new(center.x, center.y - room_size + 1)
125                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
126                        Vec2::new(center.x, center.y + room_size - 1)
127                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
128                        (room_size - 1 - (2 * d)) as f32,
129                    )
130                    .clear();
131                painter
132                    .line(
133                        Vec2::new(center.x - room_size + 1, center.y)
134                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
135                        Vec2::new(center.x + room_size - 1, center.y)
136                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
137                        (room_size - (2 * d)) as f32,
138                    )
139                    .fill(stone_broken.clone());
140                painter
141                    .line(
142                        Vec2::new(center.x - room_size + 1, center.y)
143                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
144                        Vec2::new(center.x + room_size - 1, center.y)
145                            .with_z(plot_base - height_handle - (room_size / 3) + 3),
146                        (room_size - 1 - (2 * d)) as f32,
147                    )
148                    .clear();
149            }
150            // clear room
151            painter
152                .aabb(Aabb {
153                    min: (center - room_size).with_z(plot_base - height_handle - room_size),
154                    max: (center + room_size).with_z(plot_base - height_handle + room_size - 1),
155                })
156                .clear();
157            painter
158                .aabb(Aabb {
159                    min: (center - room_size).with_z(plot_base - height_handle - room_size),
160                    max: (center + room_size).with_z(plot_base - height_handle - room_size + 1),
161                })
162                .fill(stone.clone());
163            painter
164                .aabb(Aabb {
165                    min: (center - room_size).with_z(plot_base - height_handle - room_size + 1),
166                    max: (center + room_size).with_z(plot_base - height_handle - room_size + 2),
167                })
168                .fill(grass_fill);
169        }
170        for ruin_pos in ruin_positions {
171            // ruin models
172            lazy_static! {
173                pub static ref RUIN: AssetHandle<StructuresGroup> =
174                    PrefabStructure::load_group("site_structures.jungle_ruin.jungle_ruin");
175            }
176            let rng = RandomField::new(0).get(ruin_pos) % 62;
177            let ruin = RUIN.read();
178            let ruin = ruin[rng as usize % ruin.len()].clone();
179            painter
180                .prim(Primitive::Prefab(Box::new(ruin.clone())))
181                .translate(ruin_pos)
182                .fill(Fill::Prefab(Box::new(ruin), ruin_pos, rng));
183        }
184        if underground_chamber {
185            // entry
186            painter
187                .aabb(Aabb {
188                    min: Vec2::new(center.x - 9, center.y - 3)
189                        .with_z(plot_base - height_handle - room_size + 1),
190                    max: Vec2::new(center.x - 3, center.y + 3).with_z(plot_base + 30),
191                })
192                .clear();
193            // stairs
194            painter
195                .ramp(
196                    Aabb {
197                        min: Vec2::new(center.x - room_size, center.y - 3)
198                            .with_z(plot_base - height_handle - room_size + 1),
199                        max: Vec2::new(center.x, center.y + 3).with_z(plot_base),
200                    },
201                    Dir2::NegX,
202                )
203                .fill(stone_broken);
204            let chest_pos = Vec2::new(center.x + room_size - 2, center.y - 3)
205                .with_z(plot_base - height_handle - room_size + 1);
206            let chest = SpriteKind::DungeonChest0;
207            let cfg = SpriteCfg {
208                loot_table: Some("common.loot_tables.spot.jungle_ruin".to_owned()),
209                ..Default::default()
210            };
211            painter.rotated_sprite_with_cfg(chest_pos, chest, 0, cfg);
212        } else {
213            let chest_radius = radius / 2;
214            for n in 1..=(ruins / 4.0) as i32 {
215                let chest_pos = Vec2::new(
216                    center.x + (chest_radius as f32 * ((n as f32 * phi).cos())) as i32,
217                    center.y + (chest_radius as f32 * ((n as f32 * phi).sin())) as i32,
218                );
219                if RandomField::new(0).get(chest_pos.with_z(plot_base)) % 2 > 0 {
220                    for a in 0..8 {
221                        painter
222                            .aabb(Aabb {
223                                min: (chest_pos - 1 - a).with_z(plot_base + 5 - a),
224                                max: (chest_pos + 1 + a).with_z(plot_base + 6 - a),
225                            })
226                            .fill(if a > 1 {
227                                stone.clone()
228                            } else {
229                                weak_stone.clone()
230                            });
231                    }
232                    painter.sprite(chest_pos.with_z(plot_base + 4), SpriteKind::ChestBuried);
233                }
234            }
235        }
236
237        // npcs
238        let npc_radius = radius / 4;
239        for n in 1..=(ruins / 4.0) as i32 {
240            let npc_pos = Vec2::new(
241                center.x + (npc_radius as f32 * ((n as f32 * phi).cos())) as i32,
242                center.y + (npc_radius as f32 * ((n as f32 * phi).sin())) as i32,
243            );
244            match RandomField::new(0).get(center.with_z(plot_base)) % 6 {
245                // grave robbers
246                0 => painter.spawn(
247                    EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
248                        "common.entity.spot.dwarf_grave_robber",
249                        &mut rng,
250                        None,
251                    ),
252                ),
253                // sauroks
254                1 => painter.spawn(
255                    EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
256                        "common.entity.spot.saurok",
257                        &mut rng,
258                        None,
259                    ),
260                ),
261                // grim salvager
262                2 => painter.spawn(
263                    EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
264                        "common.entity.spot.grim_salvager",
265                        &mut rng,
266                        None,
267                    ),
268                ),
269                _ => {},
270            }
271        }
272    }
273}
274
275fn stone_color(block: BlockKind) -> Arc<dyn Fn(Vec3<i32>) -> Option<Block>> {
276    Arc::new(move |pos| {
277        Some(match (RandomField::new(0).get(pos)) % 52 {
278            0..=8 => Block::new(block, Rgb::new(92, 99, 86)),
279            9..=17 => Block::new(block, Rgb::new(83, 89, 78)),
280            18..=26 => Block::new(block, Rgb::new(75, 89, 66)),
281            27..=35 => Block::new(block, Rgb::new(79, 83, 73)),
282            36..=44 => Block::new(block, Rgb::new(66, 80, 59)),
283            _ => Block::new(block, Rgb::new(88, 94, 83)),
284        })
285    })
286}