1use super::*;
2use crate::{
3 Land,
4 util::{CARDINALS, DIAGONALS, RandomField, Sampler},
5};
6use common::{
7 generation::SpecialEntity,
8 terrain::{BlockKind, SpriteKind, sprite::Owned},
9};
10use rand::prelude::*;
11use std::{f32::consts::TAU, sync::Arc};
12use vek::*;
13
14pub struct SavannahWorkshop {
16 pub door_tile: Vec2<i32>,
18 bounds: Aabr<i32>,
20 pub(crate) alt: i32,
22}
23
24impl SavannahWorkshop {
25 pub fn generate(
26 land: &Land,
27 _rng: &mut impl Rng,
28 site: &Site,
29 door_tile: Vec2<i32>,
30 door_dir: Vec2<i32>,
31 tile_aabr: Aabr<i32>,
32 alt: Option<i32>,
33 ) -> Self {
34 let door_tile_pos = site.tile_center_wpos(door_tile);
35 let bounds = Aabr {
36 min: site.tile_wpos(tile_aabr.min),
37 max: site.tile_wpos(tile_aabr.max),
38 };
39 Self {
40 bounds,
41 door_tile: door_tile_pos,
42 alt: alt.unwrap_or_else(|| {
43 land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32 + 2
44 }),
45 }
46 }
47}
48
49impl Structure for SavannahWorkshop {
50 #[cfg(feature = "dyn-lib")]
51 #[unsafe(export_name = "as_dyn_structure_savannahworkshop")]
52 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
53 Some((Self::as_dyn_impl(self), "as_dyn_structure_savannahworkshop"))
54 }
55
56 fn door_tile(&self) -> Option<Vec2<i32>> { Some(self.door_tile) }
57
58 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
59 let base = self.alt + 1;
60 let center = self.bounds.center();
61 let sprite_fill = Fill::Sampling(Arc::new(|wpos| {
62 Some(match (RandomField::new(0).get(wpos)) % 25 {
63 0 => Block::air(SpriteKind::Bowl).with_attr(Owned(true)).unwrap(),
64 1 => Block::air(SpriteKind::VialEmpty)
65 .with_attr(Owned(true))
66 .unwrap(),
67 2 => Block::air(SpriteKind::Lantern),
68 3 => Block::air(SpriteKind::JugArabic),
69 4 => Block::air(SpriteKind::Crate)
70 .with_attr(Owned(true))
71 .unwrap(),
72 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
73 })
74 }));
75 let wood_dark = Fill::Brick(BlockKind::Misc, Rgb::new(142, 67, 27), 12);
76 let reed = Fill::Brick(BlockKind::Misc, Rgb::new(72, 55, 46), 22);
77 let clay = Fill::Brick(BlockKind::Misc, Rgb::new(209, 124, 57), 22);
78 let color = Fill::Sampling(Arc::new(|center| {
79 Some(match (RandomField::new(0).get(center)) % 7 {
80 0 => Block::new(BlockKind::GlowingRock, Rgb::new(153, 82, 40)),
81 1 => Block::new(BlockKind::GlowingRock, Rgb::new(172, 104, 57)),
82 2 => Block::new(BlockKind::GlowingRock, Rgb::new(135, 106, 100)),
83 3 => Block::new(BlockKind::GlowingRock, Rgb::new(198, 164, 139)),
84 4 => Block::new(BlockKind::GlowingRock, Rgb::new(168, 163, 157)),
85 5 => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)),
86 _ => Block::new(BlockKind::GlowingRock, Rgb::new(178, 124, 90)),
87 })
88 }));
89 let length = (10 + RandomField::new(0).get(center.with_z(base)) % 6) as i32;
90 let height = 2 * length / 3;
91 let storeys = (1 + RandomField::new(0).get(center.with_z(base)) % 2) as i32;
92 let radius = length + (length / 3);
93 let reed_var = (1 + RandomField::new(0).get(center.with_z(base)) % 4) as f32;
94 let reed_parts = 36_f32 + reed_var;
95 let phi = TAU / reed_parts;
96 painter
98 .cone(Aabb {
99 min: (center - radius).with_z(base + (storeys * height) - (height / 2) + 1),
100 max: (center + radius)
101 .with_z(base + (storeys * height) + (height / 2) - 1 + reed_var as i32),
102 })
103 .fill(reed.clone());
104 painter
105 .cone(Aabb {
106 min: (center - radius).with_z(base + (storeys * height) - (height / 2)),
107 max: (center + radius)
108 .with_z(base + (storeys * height) + (height / 2) - 2 + reed_var as i32),
109 })
110 .clear();
111 painter
113 .cylinder(Aabb {
114 min: (center - length).with_z(base - 3),
115 max: (center + length + 1).with_z(base - 2),
116 })
117 .fill(clay.clone());
118 painter
119 .cylinder(Aabb {
120 min: (center - length - 1).with_z(base - 4),
121 max: (center + length + 2).with_z(base - 3),
122 })
123 .fill(clay.clone());
124 painter
125 .cylinder(Aabb {
126 min: (center - length - 2).with_z(base - 5),
127 max: (center + length + 3).with_z(base - 4),
128 })
129 .fill(clay.clone());
130 painter
131 .cylinder(Aabb {
132 min: (center - length - 3).with_z(base - height),
133 max: (center + length + 4).with_z(base - 5),
134 })
135 .fill(clay.clone());
136 for s in 0..storeys {
138 let room = painter.cylinder(Aabb {
139 min: (center - length + 2 + s).with_z(base - 2 + (s * height)),
140 max: (center + 1 + length - 2 - s).with_z(base + height + (s * height)),
141 });
142 room.fill(clay.clone());
143 for dir in DIAGONALS {
145 let decor_pos = center + dir * (length - 2 - s);
146 let decor = painter
147 .line(
148 center.with_z(base - 1 + (s * (height + 2))),
149 decor_pos.with_z(base - 1 + (s * (height + 2))),
150 5.0,
151 )
152 .intersect(room);
153 decor.fill(color.clone());
154 painter
155 .line(
156 center.with_z(base - 1 + (s * (height + 2))),
157 decor_pos.with_z(base - 1 + (s * (height + 2))),
158 4.0,
159 )
160 .intersect(decor)
161 .fill(clay.clone());
162 }
163 }
164 painter
166 .cylinder(Aabb {
167 min: (center - length + 4).with_z(base - 2),
168 max: (center + 1 + length - 4).with_z(base + (storeys * height)),
169 })
170 .clear();
171 painter
173 .cylinder(Aabb {
174 min: (center - length + 4).with_z(base - 1),
175 max: (center + 1 + length - 4).with_z(base),
176 })
177 .fill(wood_dark.clone());
178 painter
179 .cylinder(Aabb {
180 min: (center - length + 4).with_z(base),
181 max: (center + 1 + length - 4).with_z(base + 1),
182 })
183 .fill(sprite_fill);
184
185 painter
186 .cylinder(Aabb {
187 min: (center - length + 4).with_z(base + (storeys * height) - 1),
188 max: (center + 1 + length - 4).with_z(base + (storeys * height) + 1),
189 })
190 .fill(wood_dark.clone());
191
192 for s in 0..storeys {
193 for dir in CARDINALS {
195 let frame_pos = center + dir * (length - 2 - s);
196 let clear_pos = center + dir * (length + 2 - s);
197
198 painter
199 .line(
200 center.with_z(base - 1 + (s * (height + 2))),
201 frame_pos.with_z(base - 1 + (s * (height + 2))),
202 3.0,
203 )
204 .fill(color.clone());
205 painter
206 .line(
207 center.with_z(base - 1 + (s * (height + 2))),
208 clear_pos.with_z(base - 1 + (s * (height + 2))),
209 2.0,
210 )
211 .clear();
212 }
213 }
214 painter
216 .cylinder(Aabb {
217 min: (center - length + 5).with_z(base - 2),
218 max: (center + 1 + length - 5).with_z(base + (storeys * height) + 1),
219 })
220 .clear();
221 painter
223 .cylinder(Aabb {
224 min: (center - (length / 2) - 1).with_z(base - 3),
225 max: (center + (length / 2) + 1).with_z(base - 2),
226 })
227 .fill(color);
228 painter
229 .cylinder(Aabb {
230 min: (center - (length / 2) + 1).with_z(base - 3),
231 max: (center + (length / 2) - 1).with_z(base - 2),
232 })
233 .fill(clay.clone());
234
235 for n in 1..=reed_parts as i32 {
238 let pos = Vec2::new(
239 center.x + ((radius as f32) * ((n as f32 * phi).cos())) as i32,
240 center.y + ((radius as f32) * ((n as f32 * phi).sin())) as i32,
241 );
242 painter
243 .line(
244 pos.with_z(base + (storeys * height) - (height / 2)),
245 center.with_z(base + (storeys * height) + (height / 2) + reed_var as i32),
246 1.0,
247 )
248 .fill(reed.clone());
249 }
250 painter
252 .cylinder(Aabb {
253 min: (center - 3)
254 .with_z(base - 1 + (storeys * height) + (height / 2) + reed_var as i32),
255 max: (center + 4)
256 .with_z(base + (storeys * height) + (height / 2) + reed_var as i32),
257 })
258 .fill(wood_dark);
259 painter
261 .cylinder(Aabb {
262 min: (center - 2).with_z(base + (storeys * height) - 4),
263 max: (center + 3)
264 .with_z(base + 5 * (storeys * height) + (height / 2) + reed_var as i32),
265 })
266 .clear();
267
268 painter
269 .cylinder(Aabb {
270 min: (center - 1).with_z(base - 2),
271 max: (center + 2).with_z(base - 1),
272 })
273 .fill(clay);
274 painter
275 .aabb(Aabb {
276 min: (center).with_z(base - 2),
277 max: (center + 1).with_z(base - 1),
278 })
279 .clear();
280 painter
281 .aabb(Aabb {
282 min: (center).with_z(base - 3),
283 max: (center + 1).with_z(base - 2),
284 })
285 .fill(Fill::Block(Block::air(SpriteKind::Ember)));
286
287 let mut stations = vec![
288 SpriteKind::CraftingBench,
289 SpriteKind::Forge,
290 SpriteKind::SpinningWheel,
291 SpriteKind::TanningRack,
292 SpriteKind::CookingPot,
293 SpriteKind::Cauldron,
294 SpriteKind::Loom,
295 SpriteKind::Anvil,
296 SpriteKind::DismantlingBench,
297 SpriteKind::RepairBench,
298 ];
299 let cr_pos = stations.len() as f32;
300 let phi = TAU / cr_pos;
301 'outer: for d in 0..2 {
302 let dist = 4 + d;
303 for n in 1..=cr_pos as i32 {
304 let pos = Vec2::new(
305 center.x + ((dist as f32) * ((n as f32 * phi).cos())) as i32,
306 center.y + ((dist as f32) * ((n as f32 * phi).sin())) as i32,
307 );
308 if stations.is_empty() {
309 break 'outer;
310 }
311 let cr_station = stations.swap_remove(
312 RandomField::new(0).get(pos.with_z(base)) as usize % stations.len(),
313 );
314 painter.sprite(pos.with_z(base - 2), cr_station);
315 }
316 }
317
318 painter.spawn(
319 EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5))
320 .into_special(SpecialEntity::Waypoint),
321 );
322 }
323}