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(kind) => Some(with_sprite(*kind).into_vacant().with_sprite(*kind)),
227 StructureBlock::RotatedSprite(kind, ori) => Some(
228 with_sprite(*kind)
229 .into_vacant()
230 .with_sprite(*kind)
231 .with_ori(rotate_for_units(*ori, units))
232 .unwrap_or(Block::air(*kind)),
233 ),
234 StructureBlock::EntitySpawner(_entitykind, _spawn_chance) => {
235 Some(Block::new(BlockKind::Air, Rgb::new(255, 255, 255)))
236 },
237 StructureBlock::Water => Some(Block::water(SpriteKind::Empty)),
238 StructureBlock::GreenSludge => Some(Block::water(SpriteKind::Empty)),
240 StructureBlock::Liana => Some(with_sprite(SpriteKind::Liana)),
243 StructureBlock::Fruit => {
244 if field.get(pos + structure_pos) % 24 == 0 {
245 Some(with_sprite(SpriteKind::Beehive))
246 } else if field.get(pos + structure_pos + 1) % 3 == 0 {
247 Some(with_sprite(SpriteKind::Apple))
248 } else {
249 None
250 }
251 },
252 StructureBlock::Coconut => {
253 if field.get(pos + structure_pos) % 3 > 0 {
254 None
255 } else {
256 Some(with_sprite(SpriteKind::Coconut))
257 }
258 },
259 StructureBlock::Chest => {
260 let old_block = with_sprite(SpriteKind::Empty);
261 let block = if old_block.is_fluid() {
262 old_block
263 } else {
264 Block::air(SpriteKind::Empty)
265 };
266 if field.chance(pos + structure_pos, 0.5) {
267 Some(block)
268 } else {
269 Some(block.with_sprite(SpriteKind::Chest))
270 }
271 },
272 StructureBlock::Log => Some(Block::new(BlockKind::Wood, Rgb::new(60, 30, 0))),
273 StructureBlock::TemperateLeaves
275 | StructureBlock::PineLeaves
276 | StructureBlock::FrostpineLeaves
277 | StructureBlock::PalmLeavesInner
278 | StructureBlock::PalmLeavesOuter
279 | StructureBlock::Acacia
280 | StructureBlock::Mangrove
281 | StructureBlock::Chestnut
282 | StructureBlock::Baobab
283 | StructureBlock::MapleLeaves
284 | StructureBlock::CherryLeaves
285 | StructureBlock::AutumnLeaves => {
286 if calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas))
287 && field.chance(pos + structure_pos, 0.025)
288 {
289 Some(Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)))
290 } else if calendar.is_some_and(|c| c.is_event(CalendarEvent::Halloween))
291 && (*sblock == StructureBlock::TemperateLeaves
292 || *sblock == StructureBlock::Chestnut
293 || *sblock == StructureBlock::CherryLeaves)
294 {
295 crate::all::leaf_color(index, structure_seed, lerp, &StructureBlock::AutumnLeaves)
296 .map(|col| Block::new(BlockKind::Leaves, col))
297 } else {
298 crate::all::leaf_color(index, structure_seed, lerp, sblock)
299 .map(|col| Block::new(BlockKind::Leaves, col))
300 }
301 },
302 StructureBlock::BirchWood => {
303 let wpos = pos + structure_pos;
304 if field.chance(
305 (wpos + Vec3::new(wpos.z, wpos.z, 0) / 2)
306 / Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1),
307 0.25,
308 ) && wpos.z % 2 == 0
309 {
310 Some(Block::new(BlockKind::Wood, Rgb::new(70, 35, 25)))
311 } else {
312 Some(Block::new(BlockKind::Wood, Rgb::new(220, 170, 160)))
313 }
314 },
315 StructureBlock::Keyhole(consumes) => {
316 return Some((
317 Block::air(SpriteKind::Keyhole),
318 Some(SpriteCfg {
319 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
320 consumes.clone(),
321 ))),
322 ..SpriteCfg::default()
323 }),
324 ));
325 },
326 StructureBlock::Sign(content, ori) => {
327 return Some((
328 Block::air(SpriteKind::Sign)
329 .with_ori(rotate_for_units(*ori, units))
330 .expect("signs can always be rotated"),
331 Some(SpriteCfg {
332 content: Some(content.clone()),
333 ..SpriteCfg::default()
334 }),
335 ));
336 },
337 StructureBlock::BoneKeyhole(consumes) => {
338 return Some((
339 Block::air(SpriteKind::BoneKeyhole),
340 Some(SpriteCfg {
341 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
342 consumes.clone(),
343 ))),
344 ..SpriteCfg::default()
345 }),
346 ));
347 },
348 StructureBlock::HaniwaKeyhole(consumes) => {
349 return Some((
350 Block::air(SpriteKind::HaniwaKeyhole),
351 Some(SpriteCfg {
352 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
353 consumes.clone(),
354 ))),
355 ..SpriteCfg::default()
356 }),
357 ));
358 },
359 StructureBlock::KeyholeBars(consumes) => {
360 return Some((
361 Block::air(SpriteKind::KeyholeBars),
362 Some(SpriteCfg {
363 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
364 consumes.clone(),
365 ))),
366 ..SpriteCfg::default()
367 }),
368 ));
369 },
370 StructureBlock::GlassKeyhole(consumes) => {
371 return Some((
372 Block::air(SpriteKind::GlassKeyhole),
373 Some(SpriteCfg {
374 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
375 consumes.clone(),
376 ))),
377 ..SpriteCfg::default()
378 }),
379 ));
380 },
381 StructureBlock::TerracottaKeyhole(consumes) => {
382 return Some((
383 Block::air(SpriteKind::TerracottaKeyhole),
384 Some(SpriteCfg {
385 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
386 consumes.clone(),
387 ))),
388 ..SpriteCfg::default()
389 }),
390 ));
391 },
392 StructureBlock::SahaginKeyhole(consumes) => {
393 return Some((
394 Block::air(SpriteKind::SahaginKeyhole),
395 Some(SpriteCfg {
396 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
397 consumes.clone(),
398 ))),
399 ..SpriteCfg::default()
400 }),
401 ));
402 },
403 StructureBlock::VampireKeyhole(consumes) => {
404 return Some((
405 Block::air(SpriteKind::VampireKeyhole),
406 Some(SpriteCfg {
407 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
408 consumes.clone(),
409 ))),
410 ..SpriteCfg::default()
411 }),
412 ));
413 },
414
415 StructureBlock::MyrmidonKeyhole(consumes) => {
416 return Some((
417 Block::air(SpriteKind::MyrmidonKeyhole),
418 Some(SpriteCfg {
419 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
420 consumes.clone(),
421 ))),
422 ..SpriteCfg::default()
423 }),
424 ));
425 },
426 StructureBlock::MinotaurKeyhole(consumes) => {
427 return Some((
428 Block::air(SpriteKind::MinotaurKeyhole),
429 Some(SpriteCfg {
430 unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
431 consumes.clone(),
432 ))),
433 ..SpriteCfg::default()
434 }),
435 ));
436 },
437 StructureBlock::RedwoodWood => {
438 let wpos = pos + structure_pos;
439 if (wpos.x / 2 + wpos.y) % 5 > 1 && ((wpos.x + 1) / 2 + wpos.y + 2) % 5 > 1 {
440 Some(Block::new(BlockKind::Wood, Rgb::new(80, 40, 10)))
441 } else {
442 Some(Block::new(BlockKind::Wood, Rgb::new(110, 55, 10)))
443 }
444 },
445 };
446
447 Some((block?, None))
448}