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