1use crate::{
2 CONFIG, IndexRef,
3 column::{ColumnGen, ColumnSample},
4 util::{FastNoise, RandomField, Sampler, SmallCache},
5};
6use common::{
7 calendar::{Calendar, CalendarEvent},
8 comp::item::ItemDefinitionIdOwned,
9 terrain::{
10 Block, BlockKind, SpriteCfg, SpriteKind, UnlockKind,
11 structure::{self, StructureBlock},
12 },
13};
14use core::ops::{Div, Mul, Range};
15use rand::{Rng, seq::SliceRandom, thread_rng};
16use serde::Deserialize;
17use vek::*;
18
19type Gradients = Vec<Range<(u8, u8, u8)>>;
20
21#[derive(Deserialize)]
22pub struct Colors {
23 pub structure_blocks: structure::structure_block::PureCases<Option<Gradients>>,
27}
28
29pub struct BlockGen<'a> {
30 pub column_gen: ColumnGen<'a>,
31}
32
33impl<'a> BlockGen<'a> {
34 pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_gen } }
35
36 pub fn sample_column<'b>(
37 column_gen: &ColumnGen<'a>,
38 cache: &'b mut SmallCache<Vec2<i32>, Option<ColumnSample<'a>>>,
39 wpos: Vec2<i32>,
40 index: IndexRef<'a>,
41 calendar: Option<&'a Calendar>,
42 ) -> Option<&'b ColumnSample<'a>> {
43 cache
44 .get(wpos, |wpos| column_gen.get((wpos, index, calendar)))
45 .as_ref()
46 }
47
48 pub fn get_z_cache(
49 &mut self,
50 wpos: Vec2<i32>,
51 index: IndexRef<'a>,
52 calendar: Option<&'a Calendar>,
53 ) -> Option<ZCache<'a>> {
54 let BlockGen { column_gen } = self;
55
56 let sample = column_gen.get((wpos, index, calendar))?;
58
59 Some(ZCache { sample, calendar })
60 }
61
62 pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
63 let z_cache = z_cache?;
64 let sample = &z_cache.sample;
65 let &ColumnSample {
66 alt,
67 basement,
68 chaos,
69 water_level,
70 surface_color,
71 sub_surface_color,
72 stone_col,
73 snow_cover,
74 cliff_offset,
75 cliff_height,
76 ice_depth,
77 ..
78 } = sample;
79
80 let wposf = wpos.map(|e| e as f64);
81
82 let water = Block::new(BlockKind::Water, Rgb::zero());
84 let grass_depth = (1.5 + 2.0 * chaos).min(alt - basement);
85 if (wposf.z as f32) < alt - grass_depth {
86 let stone_factor = (alt - grass_depth - wposf.z as f32) * 0.15;
87 let col = Lerp::lerp(
88 sub_surface_color,
89 stone_col.map(|e| e as f32 / 255.0),
90 stone_factor,
91 )
92 .map(|e| (e * 255.0) as u8);
93
94 if stone_factor >= 0.5 {
95 if wposf.z as f32 > alt - cliff_offset.max(0.0) {
96 if cliff_offset.max(0.0)
97 > cliff_height
98 - (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5
99 + 0.5)
100 * (alt - grass_depth - wposf.z as f32)
101 .mul(0.25)
102 .clamped(0.0, 8.0)
103 {
104 Some(Block::empty())
105 } else {
106 let col = Lerp::lerp(
107 col.map(|e| e as f32),
108 col.map(|e| e as f32) * 0.7,
109 (wposf.z as f32 - basement * 0.3).div(2.0).sin() * 0.5 + 0.5,
110 )
111 .map(|e| e as u8);
112 Some(Block::new(BlockKind::Rock, col))
113 }
114 } else {
115 Some(Block::new(BlockKind::Rock, col))
116 }
117 } else {
118 Some(Block::new(BlockKind::Earth, col))
119 }
120 } else if wposf.z as i32 <= alt as i32 {
121 let grass_factor = (wposf.z as f32 - (alt - grass_depth))
122 .div(grass_depth)
123 .sqrt();
124 Some(if water_level > alt.ceil() {
126 Block::new(
127 BlockKind::Sand,
128 sub_surface_color.map(|e| (e * 255.0) as u8),
129 )
130 } else {
131 let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
132 if grass_factor < 0.7 {
133 Block::new(BlockKind::Earth, col.map(|e| (e * 255.0) as u8))
134 } else if snow_cover {
135 Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8))
137 } else {
138 Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8))
139 }
140 })
141 } else {
142 None
143 }
144 .or_else(|| {
145 let over_water = alt < water_level;
146 if over_water && (wposf.z as f32 - water_level).abs() < ice_depth {
148 Some(Block::new(BlockKind::Ice, CONFIG.ice_color))
149 } else if (wposf.z as f32) < water_level {
150 Some(water)
152 } else {
153 None
154 }
155 })
156 }
157}
158
159pub struct ZCache<'a> {
160 pub sample: ColumnSample<'a>,
161 pub calendar: Option<&'a Calendar>,
162}
163
164impl ZCache<'_> {
165 pub fn get_z_limits(&self) -> (f32, f32) {
166 let min = self.sample.alt
167 - (self.sample.chaos.min(1.0) * 16.0)
168 - self.sample.cliff_offset.max(0.0);
169 let min = min - 4.0;
170
171 let warp = self.sample.chaos * 32.0;
172
173 let ground_max = self.sample.alt + warp + 2.0;
174
175 let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth);
176
177 (min, max)
178 }
179}
180
181fn ori_of_unit(unit: &Vec2<i32>) -> u8 {
182 if unit.y != 0 {
183 if unit.y < 0 { 6 } else { 2 }
184 } else if unit.x < 0 {
185 4
186 } else {
187 0
188 }
189}
190
191fn rotate_for_units(ori: u8, units: &Vec2<Vec2<i32>>) -> u8 {
192 let ox = ori_of_unit(&units.x);
193 let oy = ori_of_unit(&units.y);
194 let positive = ((ox + 8 - oy) & 4) != 0;
195 if positive {
196 (ox + ori + if (ox & 2) != 0 { 4 } else { 0 }) % 8
198 } else {
199 (ox + 8 - ori) % 8
200 }
201}
202
203pub fn block_from_structure<'a>(
208 index: IndexRef,
209 sblock: &'a StructureBlock,
210 pos: Vec3<i32>,
211 structure_pos: Vec2<i32>,
212 structure_seed: u32,
213 sample: &ColumnSample,
214 mut with_sprite: impl FnMut(SpriteKind) -> Block,
215 calendar: Option<&Calendar>,
216 units: &Vec2<Vec2<i32>>,
217) -> Option<(Block, Option<SpriteCfg>, Option<&'a str>)> {
218 let field = RandomField::new(structure_seed);
219
220 let lerp = field.get_f32(Vec3::from(structure_pos)) * 0.8 + field.get_f32(pos) * 0.2;
221
222 match sblock {
223 StructureBlock::None => None,
224 StructureBlock::Hollow => Some((Block::air(SpriteKind::Empty), None, None)),
225 StructureBlock::Grass => Some((
226 Block::new(
227 BlockKind::Grass,
228 sample.surface_color.map(|e| (e * 255.0) as u8),
229 ),
230 None,
231 None,
232 )),
233 StructureBlock::Normal(color) => Some((Block::new(BlockKind::Misc, *color), None, None)),
234 StructureBlock::Filled(kind, color) => Some((Block::new(*kind, *color), None, None)),
235 StructureBlock::Sprite(sprite) => Some((sprite.get_block(with_sprite), None, None)),
236 StructureBlock::SpriteWithCfg(sprite, sprite_cfg) => Some((
237 sprite.get_block(with_sprite),
238 Some(sprite_cfg.clone()),
239 None,
240 )),
241 StructureBlock::EntitySpawner(entity_path, spawn_chance) => {
242 let mut rng = thread_rng();
243 if rng.gen::<f32>() < *spawn_chance {
244 Some((
246 Block::new(BlockKind::Air, Rgb::new(255, 255, 255)),
247 None,
248 Some(entity_path.as_str()),
249 ))
250 } else {
251 Some((
253 Block::new(BlockKind::Air, Rgb::new(255, 255, 255)),
254 None,
255 None,
256 ))
257 }
258 },
259 StructureBlock::Water => Some((Block::water(SpriteKind::Empty), None, None)),
260 StructureBlock::GreenSludge => Some((Block::water(SpriteKind::Empty), None, None)),
262 StructureBlock::Liana => Some((with_sprite(SpriteKind::Liana), None, None)),
265 StructureBlock::Fruit => {
266 if field.get(pos + structure_pos) % 24 == 0 {
267 Some((with_sprite(SpriteKind::Beehive), None, None))
268 } else if field.get(pos + structure_pos + 1) % 3 == 0 {
269 Some((with_sprite(SpriteKind::Apple), None, None))
270 } else {
271 None
272 }
273 },
274 StructureBlock::Coconut => {
275 if field.get(pos + structure_pos) % 3 > 0 {
276 None
277 } else {
278 Some((with_sprite(SpriteKind::Coconut), None, None))
279 }
280 },
281 StructureBlock::MaybeChest => {
282 let old_block = with_sprite(SpriteKind::Empty);
283 let block = if old_block.is_fluid() {
284 old_block
285 } else {
286 Block::air(SpriteKind::Empty)
287 };
288 if field.chance(pos + structure_pos, 0.5) {
289 Some((block, None, None))
290 } else {
291 Some((block.with_sprite(SpriteKind::Chest), None, None))
292 }
293 },
294 StructureBlock::Log => Some((Block::new(BlockKind::Wood, Rgb::new(60, 30, 0)), None, None)),
295 StructureBlock::TemperateLeaves
297 | StructureBlock::PineLeaves
298 | StructureBlock::FrostpineLeaves
299 | StructureBlock::PalmLeavesInner
300 | StructureBlock::PalmLeavesOuter
301 | StructureBlock::Acacia
302 | StructureBlock::Mangrove
303 | StructureBlock::Chestnut
304 | StructureBlock::Baobab
305 | StructureBlock::MapleLeaves
306 | StructureBlock::CherryLeaves
307 | StructureBlock::AutumnLeaves => {
308 if calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas))
309 && field.chance(pos + structure_pos, 0.025)
310 {
311 Some((
312 Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)),
313 None,
314 None,
315 ))
316 } else if calendar.is_some_and(|c| c.is_event(CalendarEvent::Halloween))
317 && (*sblock == StructureBlock::TemperateLeaves
318 || *sblock == StructureBlock::Chestnut
319 || *sblock == StructureBlock::CherryLeaves)
320 {
321 crate::all::leaf_color(index, structure_seed, lerp, &StructureBlock::AutumnLeaves)
322 .map(|col| (Block::new(BlockKind::Leaves, col), None, None))
323 } else {
324 crate::all::leaf_color(index, structure_seed, lerp, sblock)
325 .map(|col| (Block::new(BlockKind::Leaves, col), None, None))
326 }
327 },
328 StructureBlock::BirchWood => {
329 let wpos = pos + structure_pos;
330 if field.chance(
331 (wpos + Vec3::new(wpos.z, wpos.z, 0) / 2)
332 / Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1),
333 0.25,
334 ) && wpos.z % 2 == 0
335 {
336 Some((
337 Block::new(BlockKind::Wood, Rgb::new(70, 35, 25)),
338 None,
339 None,
340 ))
341 } else {
342 Some((
343 Block::new(BlockKind::Wood, Rgb::new(220, 170, 160)),
344 None,
345 None,
346 ))
347 }
348 },
349 StructureBlock::Keyhole(consumes) => Some((
350 Block::air(SpriteKind::Keyhole),
351 Some(SpriteCfg {
352 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
353 consumes.clone(),
354 ))),
355 ..SpriteCfg::default()
356 }),
357 None,
358 )),
359 StructureBlock::Sign(content, ori) => Some((
360 Block::air(SpriteKind::Sign)
361 .with_ori(rotate_for_units(*ori, units))
362 .expect("signs can always be rotated"),
363 Some(SpriteCfg {
364 content: Some(content.clone()),
365 ..SpriteCfg::default()
366 }),
367 None,
368 )),
369 StructureBlock::BoneKeyhole(consumes) => Some((
370 Block::air(SpriteKind::BoneKeyhole),
371 Some(SpriteCfg {
372 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
373 consumes.clone(),
374 ))),
375 ..SpriteCfg::default()
376 }),
377 None,
378 )),
379 StructureBlock::HaniwaKeyhole(consumes) => Some((
380 Block::air(SpriteKind::HaniwaKeyhole),
381 Some(SpriteCfg {
382 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
383 consumes.clone(),
384 ))),
385 ..SpriteCfg::default()
386 }),
387 None,
388 )),
389 StructureBlock::KeyholeBars(consumes) => Some((
390 Block::air(SpriteKind::KeyholeBars),
391 Some(SpriteCfg {
392 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
393 consumes.clone(),
394 ))),
395 ..SpriteCfg::default()
396 }),
397 None,
398 )),
399 StructureBlock::GlassKeyhole(consumes) => Some((
400 Block::air(SpriteKind::GlassKeyhole),
401 Some(SpriteCfg {
402 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
403 consumes.clone(),
404 ))),
405 ..SpriteCfg::default()
406 }),
407 None,
408 )),
409 StructureBlock::TerracottaKeyhole(consumes) => Some((
410 Block::air(SpriteKind::TerracottaKeyhole),
411 Some(SpriteCfg {
412 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
413 consumes.clone(),
414 ))),
415 ..SpriteCfg::default()
416 }),
417 None,
418 )),
419 StructureBlock::SahaginKeyhole(consumes) => Some((
420 Block::air(SpriteKind::SahaginKeyhole),
421 Some(SpriteCfg {
422 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
423 consumes.clone(),
424 ))),
425 ..SpriteCfg::default()
426 }),
427 None,
428 )),
429 StructureBlock::VampireKeyhole(consumes) => Some((
430 Block::air(SpriteKind::VampireKeyhole),
431 Some(SpriteCfg {
432 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
433 consumes.clone(),
434 ))),
435 ..SpriteCfg::default()
436 }),
437 None,
438 )),
439
440 StructureBlock::MyrmidonKeyhole(consumes) => Some((
441 Block::air(SpriteKind::MyrmidonKeyhole),
442 Some(SpriteCfg {
443 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
444 consumes.clone(),
445 ))),
446 ..SpriteCfg::default()
447 }),
448 None,
449 )),
450 StructureBlock::MinotaurKeyhole(consumes) => Some((
451 Block::air(SpriteKind::MinotaurKeyhole),
452 Some(SpriteCfg {
453 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
454 consumes.clone(),
455 ))),
456 ..SpriteCfg::default()
457 }),
458 None,
459 )),
460 StructureBlock::RedwoodWood => {
461 let wpos = pos + structure_pos;
462 if (wpos.x / 2 + wpos.y) % 5 > 1 && ((wpos.x + 1) / 2 + wpos.y + 2) % 5 > 1 {
463 Some((
464 Block::new(BlockKind::Wood, Rgb::new(80, 40, 10)),
465 None,
466 None,
467 ))
468 } else {
469 Some((
470 Block::new(BlockKind::Wood, Rgb::new(110, 55, 10)),
471 None,
472 None,
473 ))
474 }
475 },
476 StructureBlock::Choice(block_table) => block_table
477 .choose_weighted(&mut rand::thread_rng(), |(w, _)| *w)
478 .map(|(_, item)| {
479 block_from_structure(
480 index,
481 item,
482 pos,
483 structure_pos,
484 structure_seed,
485 sample,
486 with_sprite,
487 calendar,
488 units,
489 )
490 })
491 .unwrap_or(None),
492 }
493}