use std::collections::HashMap;
use yog_data::Schema;
#[derive(Schema, Debug, Clone, PartialEq)]
pub struct YogDimensionTypeDef {
pub min_y: i32,
pub height: i32,
pub logical_height: i32,
pub has_sky_light: bool,
pub sky_light_updates: bool,
pub ambient_light: f32,
pub coordinate_scale: f32,
pub ultrawarm: bool,
pub beds_explode: bool,
pub respawn_anchors_explode: bool,
pub piglin_safe: bool,
pub natural: bool,
pub has_ceiling: bool,
pub effects: String,
pub has_sky: bool,
pub has_clouds: bool,
pub cloud_height: f64,
pub has_fog: bool,
pub fog_color: Option<i32>,
pub sky_color: Option<i32>,
pub water_color: Option<i32>,
pub water_fog_color: Option<i32>,
}
const OVERWORLD_EFFECTS: &str = "overworld";
const OVERWORLD_CLOUD_HEIGHT: f64 = 192.0;
impl Default for YogDimensionTypeDef {
fn default() -> Self {
Self {
min_y: -64,
height: 384,
logical_height: 384,
has_sky_light: true,
sky_light_updates: true,
ambient_light: 0.0,
coordinate_scale: 1.0,
ultrawarm: false,
beds_explode: false,
respawn_anchors_explode: false,
piglin_safe: false,
natural: true,
has_ceiling: false,
effects: OVERWORLD_EFFECTS.to_owned(),
has_sky: true,
has_clouds: true,
cloud_height: OVERWORLD_CLOUD_HEIGHT,
has_fog: false,
fog_color: None,
sky_color: None,
water_color: None,
water_fog_color: None,
}
}
}
impl YogDimensionTypeDef {
pub fn min_y(mut self, v: i32) -> Self {
self.min_y = v;
self
}
pub fn height(mut self, v: i32) -> Self {
self.height = v;
self.logical_height = v;
self
}
pub fn logical_height(mut self, v: i32) -> Self {
self.logical_height = v;
self
}
pub fn has_sky_light(mut self, v: bool) -> Self {
self.has_sky_light = v;
self
}
pub fn sky_light_updates(mut self, v: bool) -> Self {
self.sky_light_updates = v;
self
}
pub fn ambient_light(mut self, v: f32) -> Self {
self.ambient_light = v;
self
}
pub fn coordinate_scale(mut self, v: f32) -> Self {
self.coordinate_scale = v;
self
}
pub fn ultrawarm(mut self, v: bool) -> Self {
self.ultrawarm = v;
self
}
pub fn beds_explode(mut self, v: bool) -> Self {
self.beds_explode = v;
self
}
pub fn respawn_anchors_explode(mut self, v: bool) -> Self {
self.respawn_anchors_explode = v;
self
}
pub fn piglin_safe(mut self, v: bool) -> Self {
self.piglin_safe = v;
self
}
pub fn natural(mut self, v: bool) -> Self {
self.natural = v;
self
}
pub fn has_ceiling(mut self, v: bool) -> Self {
self.has_ceiling = v;
self
}
pub fn effects(mut self, v: impl Into<String>) -> Self {
self.effects = v.into();
self
}
pub fn has_sky(mut self, v: bool) -> Self {
self.has_sky = v;
self
}
pub fn has_clouds(mut self, v: bool) -> Self {
self.has_clouds = v;
self
}
pub fn cloud_height(mut self, v: f64) -> Self {
self.cloud_height = v;
self
}
pub fn has_fog(mut self, v: bool) -> Self {
self.has_fog = v;
self
}
pub fn fog_color(mut self, v: i32) -> Self {
self.fog_color = Some(v);
self
}
pub fn sky_color(mut self, v: i32) -> Self {
self.sky_color = Some(v);
self
}
pub fn water_color(mut self, v: i32) -> Self {
self.water_color = Some(v);
self
}
pub fn water_fog_color(mut self, v: i32) -> Self {
self.water_fog_color = Some(v);
self
}
}
#[derive(Schema, Debug, Clone, PartialEq)]
pub struct YogDimensionDef {
pub id: String,
pub dimension_type: YogDimensionTypeDef,
pub generator_type: Option<String>,
pub biome: String,
pub structures: bool,
pub features: bool,
pub mob_spawning: bool,
#[data(default)]
pub extra: HashMap<String, String>,
}
impl Default for YogDimensionDef {
fn default() -> Self {
Self {
id: String::new(),
dimension_type: YogDimensionTypeDef::default(),
generator_type: None,
biome: yog_ids::game::PLAINS.into(),
structures: true,
features: true,
mob_spawning: true,
extra: HashMap::new(),
}
}
}
impl YogDimensionDef {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
..Self::default()
}
}
pub fn dimension_type(mut self, dimension_type: YogDimensionTypeDef) -> Self {
self.dimension_type = dimension_type;
self
}
pub fn generator_type(mut self, generator_type_id: impl Into<String>) -> Self {
self.generator_type = Some(generator_type_id.into());
self
}
pub fn biome(mut self, biome: impl Into<String>) -> Self {
self.biome = biome.into();
self
}
pub fn structures(mut self, on: bool) -> Self {
self.structures = on;
self
}
pub fn features(mut self, on: bool) -> Self {
self.features = on;
self
}
pub fn mob_spawning(mut self, on: bool) -> Self {
self.mob_spawning = on;
self
}
pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.extra.insert(key.into(), value.into());
self
}
}
pub const CHUNK_WIDTH: i32 = 16;
#[derive(yog_data::Schema, Debug, Clone, Default, PartialEq)]
pub struct GeneratedChunk {
pub generator: String,
pub chunk_x: i32,
pub chunk_z: i32,
pub min_y: i32,
pub height: i32,
#[data(default)]
pub palette: Vec<String>,
#[data(with = "yog_data::with::Raw")]
pub cells: Vec<u8>,
}
pub const CELL: usize = std::mem::size_of::<u16>();
impl GeneratedChunk {
fn cell(&self, x: i32, y: i32, z: i32) -> Option<usize> {
let inside = (0..CHUNK_WIDTH).contains(&x)
&& (0..CHUNK_WIDTH).contains(&z)
&& (self.min_y..self.min_y + self.height).contains(&y);
inside.then(|| (((y - self.min_y) * CHUNK_WIDTH + z) * CHUNK_WIDTH + x) as usize)
}
pub fn set_block(&mut self, x: i32, y: i32, z: i32, block_id: &str) -> bool {
let Some(cell) = self.cell(x, y, z) else {
return false;
};
let index = match self.palette.iter().position(|b| b == block_id) {
Some(i) => i,
None => {
self.palette.push(block_id.to_owned());
self.palette.len() - 1
}
};
let Ok(value) = u16::try_from(index + 1) else {
return false;
};
if self.cells.is_empty() {
self.cells = vec![0; (CHUNK_WIDTH * CHUNK_WIDTH * self.height.max(0)) as usize * CELL];
}
self.cells[cell * CELL..(cell + 1) * CELL].copy_from_slice(&value.to_le_bytes());
true
}
pub fn block(&self, x: i32, y: i32, z: i32) -> Option<&str> {
let cell = self.cell(x, y, z)?;
let bytes = self.cells.get(cell * CELL..(cell + 1) * CELL)?;
let value = u16::from_le_bytes([bytes[0], bytes[1]]) as usize;
self.palette.get(value.checked_sub(1)?).map(String::as_str)
}
pub fn placed(&self) -> impl Iterator<Item = (i32, i32, i32, &str)> {
let width = CHUNK_WIDTH as usize;
self.cells
.as_chunks::<CELL>()
.0
.iter()
.enumerate()
.filter_map(move |(cell, b)| {
let value = u16::from_le_bytes(*b) as usize;
let id = self.palette.get(value.checked_sub(1)?)?;
let (x, z, y) = (cell % width, cell / width % width, cell / (width * width));
Some((x as i32, y as i32 + self.min_y, z as i32, id.as_str()))
})
}
}
#[cfg(test)]
mod chunk_tests {
use super::*;
#[test]
fn placed_blocks_share_a_palette_and_stay_in_the_column() {
let mut chunk = GeneratedChunk {
min_y: -64,
height: 384,
..Default::default()
};
assert!(
chunk.cells.is_empty(),
"nothing allocated before the first block"
);
assert!(chunk.set_block(0, -64, 15, "minecraft:bedrock"));
assert!(chunk.set_block(1, 0, 1, "minecraft:stone"));
assert!(
chunk.set_block(1, 0, 1, "minecraft:diamond_ore"),
"a later block replaces an earlier one"
);
assert!(chunk.set_block(2, 0, 2, "minecraft:stone"));
assert!(
!chunk.set_block(16, 0, 0, "minecraft:stone"),
"x past the column"
);
assert!(
!chunk.set_block(0, 320, 0, "minecraft:stone"),
"y past the top"
);
assert_eq!(
chunk.palette,
[
"minecraft:bedrock",
"minecraft:stone",
"minecraft:diamond_ore"
]
);
assert_eq!(chunk.block(1, 0, 1), Some("minecraft:diamond_ore"));
assert_eq!(chunk.block(3, 0, 3), None);
let placed: Vec<_> = chunk.placed().collect();
assert_eq!(
placed,
[
(0, -64, 15, "minecraft:bedrock"),
(1, 0, 1, "minecraft:diamond_ore"),
(2, 0, 2, "minecraft:stone")
]
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dimension_def_roundtrips_through_yog_data() {
let def = YogDimensionDef::new("mymod:my_dim")
.dimension_type(
YogDimensionTypeDef::default()
.ultrawarm(true)
.effects("nether"),
)
.generator_type("mymod:custom_terrain")
.with_extra("note", "test");
let back: YogDimensionDef =
yog_data::read_bytes(&yog_data::to_bytes(&def)).expect("a def reads back");
assert_eq!(back, def);
assert_eq!(back.id, "mymod:my_dim");
assert!(back.dimension_type.ultrawarm);
assert_eq!(back.dimension_type.effects, "nether");
assert_eq!(back.generator_type.as_deref(), Some("mymod:custom_terrain"));
assert_eq!(back.extra.get("note").map(String::as_str), Some("test"));
}
#[test]
fn dimension_def_can_target_a_vanilla_id_to_patch_it() {
let def =
YogDimensionDef::new("minecraft:overworld").generator_type("mymod:custom_terrain");
assert_eq!(def.id, "minecraft:overworld");
assert_eq!(def.generator_type.as_deref(), Some("mymod:custom_terrain"));
}
}