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::prelude::IndexedRandom;
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 block_for_sprite: impl Fn() -> 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 let with_sprite = |sprite| block_for_sprite().with_sprite(sprite);
223
224 match sblock {
225 StructureBlock::None => None,
226 StructureBlock::Hollow => Some((Block::air(SpriteKind::Empty), None, None)),
227 StructureBlock::Grass => Some((
228 Block::new(
229 BlockKind::Grass,
230 sample.surface_color.map(|e| (e * 255.0) as u8),
231 ),
232 None,
233 None,
234 )),
235 StructureBlock::Normal(color) => Some((Block::new(BlockKind::Misc, *color), None, None)),
236 StructureBlock::Filled(kind, color) => Some((Block::new(*kind, *color), None, None)),
237 StructureBlock::Sprite(sprite) => Some((
238 sprite
239 .apply_to_block(block_for_sprite())
240 .unwrap_or_else(|b| b),
241 None,
242 None,
243 )),
244 StructureBlock::SpriteWithCfg(sprite, sprite_cfg) => {
245 let (block, sprite_cfg) = match sprite.apply_to_block(block_for_sprite()) {
246 Ok(b) => (b, Some(sprite_cfg.clone())),
247 Err(b) => (b, None),
248 };
249 Some((block, sprite_cfg, None))
250 },
251 StructureBlock::EntitySpawner(entity_path, spawn_chance) => {
252 if field.chance(pos + structure_pos, *spawn_chance) {
253 Some((
255 Block::new(BlockKind::Air, Rgb::new(255, 255, 255)),
256 None,
257 Some(entity_path.as_str()),
258 ))
259 } else {
260 Some((
262 Block::new(BlockKind::Air, Rgb::new(255, 255, 255)),
263 None,
264 None,
265 ))
266 }
267 },
268 StructureBlock::Water => Some((Block::water(SpriteKind::Empty), None, None)),
269 StructureBlock::GreenSludge => Some((Block::water(SpriteKind::Empty), None, None)),
271 StructureBlock::Liana => Some((with_sprite(SpriteKind::Liana), None, None)),
274 StructureBlock::Fruit => {
275 if field.get(pos + structure_pos) % 24 == 0 {
276 Some((with_sprite(SpriteKind::Beehive), None, None))
277 } else if field.get(pos + structure_pos + 1) % 3 == 0 {
278 Some((with_sprite(SpriteKind::Apple), None, None))
279 } else {
280 None
281 }
282 },
283 StructureBlock::Coconut => {
284 if field.get(pos + structure_pos) % 3 > 0 {
285 None
286 } else {
287 Some((with_sprite(SpriteKind::Coconut), None, None))
288 }
289 },
290 StructureBlock::MaybeChest => {
291 let old_block = with_sprite(SpriteKind::Empty);
292 let block = if old_block.is_fluid() {
293 old_block
294 } else {
295 Block::air(SpriteKind::Empty)
296 };
297 if field.chance(pos + structure_pos, 0.5) {
298 Some((block, None, None))
299 } else {
300 Some((block.with_sprite(SpriteKind::Chest), None, None))
301 }
302 },
303 StructureBlock::Log => Some((Block::new(BlockKind::Wood, Rgb::new(60, 30, 0)), None, None)),
304 StructureBlock::TemperateLeaves
306 | StructureBlock::PineLeaves
307 | StructureBlock::FrostpineLeaves
308 | StructureBlock::PalmLeavesInner
309 | StructureBlock::PalmLeavesOuter
310 | StructureBlock::Acacia
311 | StructureBlock::Mangrove
312 | StructureBlock::Chestnut
313 | StructureBlock::Baobab
314 | StructureBlock::MapleLeaves
315 | StructureBlock::CherryLeaves
316 | StructureBlock::AutumnLeaves => {
317 if calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas))
318 && field.chance(pos + structure_pos, 0.025)
319 {
320 Some((
321 Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)),
322 None,
323 None,
324 ))
325 } else if calendar.is_some_and(|c| c.is_event(CalendarEvent::Halloween))
326 && matches!(
327 *sblock,
328 StructureBlock::TemperateLeaves
329 | StructureBlock::Chestnut
330 | StructureBlock::CherryLeaves
331 )
332 {
333 crate::all::leaf_color(index, structure_seed, lerp, &StructureBlock::AutumnLeaves)
334 .map(|col| (Block::new(BlockKind::Leaves, col), None, None))
335 } else {
336 crate::all::leaf_color(index, structure_seed, lerp, sblock)
337 .map(|col| (Block::new(BlockKind::Leaves, col), None, None))
338 }
339 },
340 StructureBlock::BirchWood => {
341 let wpos = pos + structure_pos;
342 if field.chance(
343 (wpos + Vec3::new(wpos.z, wpos.z, 0) / 2)
344 / Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1),
345 0.25,
346 ) && wpos.z % 2 == 0
347 {
348 Some((
349 Block::new(BlockKind::Wood, Rgb::new(70, 35, 25)),
350 None,
351 None,
352 ))
353 } else {
354 Some((
355 Block::new(BlockKind::Wood, Rgb::new(220, 170, 160)),
356 None,
357 None,
358 ))
359 }
360 },
361 StructureBlock::Keyhole(consumes) => Some((
362 Block::air(SpriteKind::Keyhole),
363 Some(SpriteCfg {
364 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
365 consumes.clone(),
366 ))),
367 ..SpriteCfg::default()
368 }),
369 None,
370 )),
371 StructureBlock::Sign(content, ori) => Some((
372 Block::air(SpriteKind::Sign)
373 .with_ori(rotate_for_units(*ori, units))
374 .expect("signs can always be rotated"),
375 Some(SpriteCfg {
376 content: Some(content.clone()),
377 ..SpriteCfg::default()
378 }),
379 None,
380 )),
381 StructureBlock::BoneKeyhole(consumes) => Some((
382 Block::air(SpriteKind::BoneKeyhole),
383 Some(SpriteCfg {
384 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
385 consumes.clone(),
386 ))),
387 ..SpriteCfg::default()
388 }),
389 None,
390 )),
391 StructureBlock::HaniwaKeyhole(consumes) => Some((
392 Block::air(SpriteKind::HaniwaKeyhole),
393 Some(SpriteCfg {
394 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
395 consumes.clone(),
396 ))),
397 ..SpriteCfg::default()
398 }),
399 None,
400 )),
401 StructureBlock::KeyholeBars(consumes) => Some((
402 Block::air(SpriteKind::KeyholeBars),
403 Some(SpriteCfg {
404 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
405 consumes.clone(),
406 ))),
407 ..SpriteCfg::default()
408 }),
409 None,
410 )),
411 StructureBlock::GlassKeyhole(consumes) => Some((
412 Block::air(SpriteKind::GlassKeyhole),
413 Some(SpriteCfg {
414 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
415 consumes.clone(),
416 ))),
417 ..SpriteCfg::default()
418 }),
419 None,
420 )),
421 StructureBlock::TerracottaKeyhole(consumes) => Some((
422 Block::air(SpriteKind::TerracottaKeyhole),
423 Some(SpriteCfg {
424 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
425 consumes.clone(),
426 ))),
427 ..SpriteCfg::default()
428 }),
429 None,
430 )),
431 StructureBlock::SahaginKeyhole(consumes) => Some((
432 Block::air(SpriteKind::SahaginKeyhole),
433 Some(SpriteCfg {
434 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
435 consumes.clone(),
436 ))),
437 ..SpriteCfg::default()
438 }),
439 None,
440 )),
441 StructureBlock::VampireKeyhole(consumes) => Some((
442 Block::air(SpriteKind::VampireKeyhole),
443 Some(SpriteCfg {
444 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
445 consumes.clone(),
446 ))),
447 ..SpriteCfg::default()
448 }),
449 None,
450 )),
451
452 StructureBlock::MyrmidonKeyhole(consumes) => Some((
453 Block::air(SpriteKind::MyrmidonKeyhole),
454 Some(SpriteCfg {
455 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
456 consumes.clone(),
457 ))),
458 ..SpriteCfg::default()
459 }),
460 None,
461 )),
462 StructureBlock::MinotaurKeyhole(consumes) => Some((
463 Block::air(SpriteKind::MinotaurKeyhole),
464 Some(SpriteCfg {
465 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
466 consumes.clone(),
467 ))),
468 ..SpriteCfg::default()
469 }),
470 None,
471 )),
472 StructureBlock::RedwoodWood => {
473 let wpos = pos + structure_pos;
474 if (wpos.x / 2 + wpos.y) % 5 > 1 && ((wpos.x + 1) / 2 + wpos.y + 2) % 5 > 1 {
475 Some((
476 Block::new(BlockKind::Wood, Rgb::new(80, 40, 10)),
477 None,
478 None,
479 ))
480 } else {
481 Some((
482 Block::new(BlockKind::Wood, Rgb::new(110, 55, 10)),
483 None,
484 None,
485 ))
486 }
487 },
488 StructureBlock::Choice(block_table) => block_table
489 .choose_weighted(&mut rand::rng(), |(w, _)| *w)
490 .map(|(_, item)| {
491 block_from_structure(
492 index,
493 item,
494 pos,
495 structure_pos,
496 structure_seed,
497 sample,
498 block_for_sprite,
499 calendar,
500 units,
501 )
502 })
503 .unwrap_or(None),
504 }
505}