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