Skip to content

Commit 89a0981

Browse files
authored
Merge pull request #1065 from louis-e/feat/survival-prep
Feat/survival prep
2 parents d081086 + d486b5a commit 89a0981

13 files changed

Lines changed: 1472 additions & 269 deletions

File tree

src/bedrock_block_map.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ pub fn to_bedrock_block(block: Block) -> BedrockBlock {
229229
],
230230
),
231231

232+
// Mangrove leaves with persistence (1.19+)
233+
"mangrove_leaves" => BedrockBlock::with_states(
234+
"mangrove_leaves",
235+
vec![
236+
("persistent_bit", BedrockBlockStateValue::Bool(true)),
237+
("update_bit", BedrockBlockStateValue::Bool(false)),
238+
],
239+
),
240+
241+
// Azalea leaves with persistence (1.17+)
242+
"azalea_leaves" => BedrockBlock::with_states(
243+
"azalea_leaves",
244+
vec![
245+
("persistent_bit", BedrockBlockStateValue::Bool(true)),
246+
("update_bit", BedrockBlockStateValue::Bool(false)),
247+
],
248+
),
249+
232250
// Stone slab (bottom half by default)
233251
"stone_slab" => BedrockBlock::with_states(
234252
"stone_block_slab",

src/biome.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
//! Land-cover-driven biome assignment for Java Anvil chunks (1.18+).
2+
3+
use crate::coordinate_system::cartesian::XZPoint;
4+
use crate::ground::Ground;
5+
use crate::land_cover::{
6+
LC_BARE, LC_BUILT_UP, LC_CROPLAND, LC_GRASSLAND, LC_MANGROVES, LC_MOSS, LC_SHRUBLAND,
7+
LC_SNOW_ICE, LC_TREE_COVER, LC_WATER, LC_WETLAND,
8+
};
9+
use fastnbt::{LongArray, Value};
10+
use std::collections::HashMap;
11+
12+
/// Map an ESA WorldCover class to a Minecraft biome ID.
13+
pub fn biome_for_class(lc: u8, lat_deg: f64, water_dist: u8) -> &'static str {
14+
let abs_lat = lat_deg.abs();
15+
match lc {
16+
LC_TREE_COVER => {
17+
if abs_lat > 55.0 {
18+
"minecraft:taiga"
19+
} else if abs_lat < 23.5 {
20+
"minecraft:jungle"
21+
} else {
22+
"minecraft:forest"
23+
}
24+
}
25+
LC_SHRUBLAND => "minecraft:savanna",
26+
LC_GRASSLAND | LC_CROPLAND | LC_BUILT_UP => "minecraft:plains",
27+
LC_BARE => "minecraft:desert",
28+
LC_SNOW_ICE => "minecraft:snowy_plains",
29+
LC_WATER => {
30+
if water_dist >= 8 {
31+
"minecraft:ocean"
32+
} else {
33+
"minecraft:river"
34+
}
35+
}
36+
LC_WETLAND => "minecraft:swamp",
37+
LC_MANGROVES => "minecraft:mangrove_swamp",
38+
LC_MOSS => "minecraft:taiga",
39+
_ => "minecraft:plains",
40+
}
41+
}
42+
43+
pub type ChunkBiomeNbt = Value;
44+
45+
/// Build the `biomes` compound for one chunk, sampling LC at a 4x4 grid
46+
/// (4-block resolution) and packing into the Anvil 1.18+ palette+data layout.
47+
pub fn build_chunk_biome_nbt(
48+
chunk_x: i32,
49+
chunk_z: i32,
50+
ground: Option<&Ground>,
51+
center_lat_deg: f64,
52+
) -> ChunkBiomeNbt {
53+
let mut names: [&'static str; 16] = ["minecraft:plains"; 16];
54+
55+
if let Some(g) = ground {
56+
for zi in 0..4i32 {
57+
for xi in 0..4i32 {
58+
let world_x = chunk_x * 16 + xi * 4 + 2;
59+
let world_z = chunk_z * 16 + zi * 4 + 2;
60+
let coord = XZPoint::new(world_x, world_z);
61+
let lc = g.cover_class(coord);
62+
let wd = g.water_distance(coord);
63+
names[(zi * 4 + xi) as usize] = biome_for_class(lc, center_lat_deg, wd);
64+
}
65+
}
66+
}
67+
68+
let mut palette: Vec<&'static str> = Vec::with_capacity(4);
69+
let mut indices: [u8; 16] = [0; 16];
70+
for (i, &name) in names.iter().enumerate() {
71+
let idx = match palette.iter().position(|p| *p == name) {
72+
Some(idx) => idx,
73+
None => {
74+
palette.push(name);
75+
palette.len() - 1
76+
}
77+
};
78+
indices[i] = idx as u8;
79+
}
80+
81+
let palette_value = Value::List(
82+
palette
83+
.iter()
84+
.map(|&s| Value::String(s.to_string()))
85+
.collect(),
86+
);
87+
88+
if palette.len() <= 1 {
89+
let mut map = HashMap::with_capacity(1);
90+
map.insert("palette".to_string(), palette_value);
91+
return Value::Compound(map);
92+
}
93+
94+
let bits = bits_per_index(palette.len());
95+
let data = pack_biome_indices(&indices, bits);
96+
97+
let mut map = HashMap::with_capacity(2);
98+
map.insert("palette".to_string(), palette_value);
99+
map.insert("data".to_string(), Value::LongArray(LongArray::new(data)));
100+
Value::Compound(map)
101+
}
102+
103+
fn bits_per_index(palette_size: usize) -> u32 {
104+
if palette_size <= 1 {
105+
0
106+
} else {
107+
(palette_size - 1).ilog2() + 1
108+
}
109+
}
110+
111+
// Post-1.16 packing: values do not straddle long boundaries.
112+
fn pack_biome_indices(indices_16: &[u8; 16], bits: u32) -> Vec<i64> {
113+
debug_assert!((1..=6).contains(&bits));
114+
let bits = bits as usize;
115+
let vals_per_long = 64 / bits;
116+
let num_longs = 64usize.div_ceil(vals_per_long);
117+
let mask: u64 = (1u64 << bits) - 1;
118+
119+
let mut longs = vec![0u64; num_longs];
120+
for cell in 0..64usize {
121+
// xz biomes repeat across y, so xz_idx = cell % 16.
122+
let xz_idx = cell % 16;
123+
let value = (indices_16[xz_idx] as u64) & mask;
124+
let long_idx = cell / vals_per_long;
125+
let bit_offset = (cell % vals_per_long) * bits;
126+
longs[long_idx] |= value << bit_offset;
127+
}
128+
longs.into_iter().map(|u| u as i64).collect()
129+
}
130+
131+
#[cfg(test)]
132+
mod tests {
133+
use super::*;
134+
135+
#[test]
136+
fn bits_per_index_table() {
137+
assert_eq!(bits_per_index(1), 0);
138+
assert_eq!(bits_per_index(2), 1);
139+
assert_eq!(bits_per_index(3), 2);
140+
assert_eq!(bits_per_index(4), 2);
141+
assert_eq!(bits_per_index(5), 3);
142+
assert_eq!(bits_per_index(8), 3);
143+
assert_eq!(bits_per_index(9), 4);
144+
assert_eq!(bits_per_index(16), 4);
145+
}
146+
147+
#[test]
148+
fn pack_alternating_1bit_fits_one_long() {
149+
let mut indices = [0u8; 16];
150+
for (i, v) in indices.iter_mut().enumerate() {
151+
*v = (i % 2) as u8;
152+
}
153+
let longs = pack_biome_indices(&indices, 1);
154+
assert_eq!(longs.len(), 1);
155+
let expected: u64 = (0..64u64).fold(0, |acc, c| acc | ((c % 2) << c));
156+
assert_eq!(longs[0] as u64, expected);
157+
}
158+
159+
#[test]
160+
fn pack_three_biomes_uses_two_longs() {
161+
let mut indices = [0u8; 16];
162+
for (i, v) in indices.iter_mut().enumerate() {
163+
*v = (i % 3) as u8;
164+
}
165+
let longs = pack_biome_indices(&indices, 2);
166+
assert_eq!(longs.len(), 2);
167+
}
168+
169+
#[test]
170+
fn pack_three_bit_pads_to_four_longs() {
171+
let indices = [4u8; 16];
172+
let longs = pack_biome_indices(&indices, 3);
173+
assert_eq!(longs.len(), 4);
174+
}
175+
176+
#[test]
177+
fn no_ground_yields_plains_palette() {
178+
let nbt = build_chunk_biome_nbt(0, 0, None, 0.0);
179+
match nbt {
180+
Value::Compound(map) => {
181+
assert!(map.contains_key("palette"));
182+
assert!(!map.contains_key("data"));
183+
}
184+
_ => panic!("expected compound"),
185+
}
186+
}
187+
188+
#[test]
189+
fn latitude_drives_tree_biome() {
190+
assert_eq!(biome_for_class(LC_TREE_COVER, 0.0, 0), "minecraft:jungle");
191+
assert_eq!(biome_for_class(LC_TREE_COVER, 40.0, 0), "minecraft:forest");
192+
assert_eq!(biome_for_class(LC_TREE_COVER, 60.0, 0), "minecraft:taiga");
193+
assert_eq!(biome_for_class(LC_TREE_COVER, -60.0, 0), "minecraft:taiga");
194+
}
195+
196+
#[test]
197+
fn water_distance_drives_river_vs_ocean() {
198+
assert_eq!(biome_for_class(LC_WATER, 0.0, 1), "minecraft:river");
199+
assert_eq!(biome_for_class(LC_WATER, 0.0, 7), "minecraft:river");
200+
assert_eq!(biome_for_class(LC_WATER, 0.0, 8), "minecraft:ocean");
201+
}
202+
}

src/block_definitions.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ impl Block {
106106

107107
pub fn name(&self) -> &str {
108108
match self.id {
109-
0 => "acacia_planks",
109+
0 => "mangrove_log",
110110
1 => "air",
111111
2 => "andesite",
112112
3 => "birch_leaves",
@@ -329,8 +329,8 @@ impl Block {
329329
230 => "cherry_log",
330330
231 => "cherry_leaves",
331331
232 => "brown_concrete_powder",
332-
233 => "orange_stained_glass",
333-
234 => "magenta_stained_glass",
332+
233 => "mangrove_leaves",
333+
234 => "azalea_leaves",
334334
235 => "potted_poppy",
335335
236 => "oak_trapdoor",
336336
237 => "oak_trapdoor",
@@ -345,9 +345,9 @@ impl Block {
345345
246 => "potted_red_tulip",
346346
247 => "potted_dandelion",
347347
248 => "potted_blue_orchid",
348-
249 => "red_sand",
349-
250 => "red_sandstone",
350-
251 => "cactus",
348+
249 => "diamond_ore",
349+
250 => "redstone_ore",
350+
251 => "lapis_ore",
351351
252 => "gray_concrete_powder",
352352
253 => "cyan_terracotta",
353353
254 => "black_wool",
@@ -1035,6 +1035,14 @@ pub const CYAN_TERRACOTTA: Block = Block::new(253);
10351035
pub const BLACK_WOOL: Block = Block::new(254);
10361036
pub const LIGHT_GRAY_WALL_BANNER: Block = Block::new(255);
10371037

1038+
pub const MANGROVE_LOG: Block = Block::new(0);
1039+
pub const MANGROVE_LEAVES: Block = Block::new(233);
1040+
pub const AZALEA_LEAVES: Block = Block::new(234);
1041+
1042+
pub const DIAMOND_ORE: Block = Block::new(249);
1043+
pub const REDSTONE_ORE: Block = Block::new(250);
1044+
pub const LAPIS_ORE: Block = Block::new(251);
1045+
10381046
/// Maps a block to a stair variant in the same colour family.
10391047
#[inline]
10401048
pub fn get_stair_block_for_material(material: Block) -> Block {

src/data_processing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ pub fn generate_world_with_options(
417417
&building_footprints,
418418
)?;
419419

420+
if args.fillground {
421+
crate::ore_generation::generate_ores(&mut editor, &xzbbox);
422+
}
423+
420424
// Carve depth into ESA water cells (water_areas.rs only covers OSM polygons).
421425
crate::water_depth::carve_lc_water_pass(
422426
&mut editor,

src/element_processing/landuse.rs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,41 @@ pub fn generate_landuse(
5656
// Get the area of the landuse element using cache
5757
let floor_area = flood_fill_cache.get_or_compute(element, args.timeout.as_ref());
5858

59+
// Cherry/FloweringOak only via the random Tree::create pool (rare).
5960
let trees_ok_to_generate: Vec<TreeType> = {
6061
let mut trees: Vec<TreeType> = vec![];
6162
if let Some(leaf_type) = element.tags.get("leaf_type") {
6263
match leaf_type.as_str() {
6364
"broadleaved" => {
6465
trees.push(TreeType::Oak);
6566
trees.push(TreeType::Birch);
67+
trees.push(TreeType::TallOak);
68+
trees.push(TreeType::Bush);
69+
trees.push(TreeType::AzaleaBush);
70+
}
71+
"needleleaved" => {
72+
trees.push(TreeType::Spruce);
73+
trees.push(TreeType::Pine);
6674
}
67-
"needleleaved" => trees.push(TreeType::Spruce),
6875
_ => {
6976
trees.push(TreeType::Oak);
7077
trees.push(TreeType::Spruce);
7178
trees.push(TreeType::Birch);
79+
trees.push(TreeType::TallOak);
80+
trees.push(TreeType::Pine);
81+
trees.push(TreeType::Bush);
82+
trees.push(TreeType::AzaleaBush);
83+
trees.push(TreeType::Willow);
7284
}
7385
}
7486
} else {
7587
trees.push(TreeType::Oak);
7688
trees.push(TreeType::Spruce);
7789
trees.push(TreeType::Birch);
90+
trees.push(TreeType::TallOak);
91+
trees.push(TreeType::Pine);
92+
trees.push(TreeType::Bush);
93+
trees.push(TreeType::AzaleaBush);
7894
}
7995
trees
8096
};
@@ -178,27 +194,32 @@ pub fn generate_landuse(
178194
}
179195
}
180196
"forest" if editor.check_for_block(x, 0, z, Some(&[GRASS_BLOCK])) => {
181-
let random_choice: i32 = rng.random_range(0..30);
182-
if random_choice == 20 {
197+
// Density-modulated spawn: thickets in some patches, clearings in others.
198+
let density = crate::ground_generation::value_noise_01(x, z, 32);
199+
let tree_threshold = ((60.0 - density * 45.0) as i32).max(5);
200+
if rng.random_range(0..tree_threshold) == 0 {
183201
let tree_type = *trees_ok_to_generate
184202
.choose(&mut rng)
185203
.unwrap_or(&TreeType::Oak);
186204
Tree::create_of_type(editor, (x, 1, z), tree_type, Some(building_footprints));
187-
} else if random_choice == 2 {
188-
let flower_block: Block = match rng.random_range(1..=6) {
189-
1 => OAK_LEAVES,
190-
2 => RED_FLOWER,
191-
3 => BLUE_FLOWER,
192-
4 => YELLOW_FLOWER,
193-
5 => FERN,
194-
_ => WHITE_FLOWER,
195-
};
196-
editor.set_block(flower_block, x, 1, z, None, None);
197-
} else if random_choice <= 12 {
198-
if rng.random_range(0..100) < 12 {
199-
editor.set_block(FERN, x, 1, z, None, None);
200-
} else {
201-
editor.set_block(GRASS, x, 1, z, None, None);
205+
} else {
206+
let random_choice: i32 = rng.random_range(0..30);
207+
if random_choice == 2 {
208+
let flower_block: Block = match rng.random_range(1..=6) {
209+
1 => OAK_LEAVES,
210+
2 => RED_FLOWER,
211+
3 => BLUE_FLOWER,
212+
4 => YELLOW_FLOWER,
213+
5 => FERN,
214+
_ => WHITE_FLOWER,
215+
};
216+
editor.set_block(flower_block, x, 1, z, None, None);
217+
} else if random_choice <= 12 {
218+
if rng.random_range(0..100) < 12 {
219+
editor.set_block(FERN, x, 1, z, None, None);
220+
} else {
221+
editor.set_block(GRASS, x, 1, z, None, None);
222+
}
202223
}
203224
}
204225
}

0 commit comments

Comments
 (0)