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 let room_size = 10;
86 let height_handle = 10;
87 if underground_chamber {
88 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 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 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 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 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 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 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 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 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 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 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}