use std::{
alloc::{alloc, dealloc, Layout},
fmt::Debug,
mem::transmute,
};
use bitflags::bitflags;
use thiserror::Error;
use crate::enums;
use cubiomes_sys::{getMinCacheSize, num_traits::FromPrimitive};
#[derive(Error, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum GeneratorError {
#[error("Biome id {0} is out of range and is not a valid biomeid")]
BiomeIDOutOfRange(i32),
#[error(
"Function getBiomeAt failed with error code -1, did you forgot to initialize the seed?"
)]
GetBiomeAtFailure,
#[error("Function genBiomes failed with error code {0}")]
GenBiomeToCacheFailure(i32),
#[error("Index out of bounds")]
IndexOutOfBounds,
#[error("Failed to convert range")]
TryFromRangeError(TryFromRangeError),
}
impl From<TryFromRangeError> for GeneratorError {
fn from(value: TryFromRangeError) -> Self {
Self::TryFromRangeError(value)
}
}
#[derive(Error, Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum TryFromRangeError {
#[error("x sixe is out of bounds for range")]
#[allow(missing_docs)]
XSizeOutOfBounds,
#[error("z size is out of bounds for range")]
#[allow(missing_docs)]
ZSizeOutOfBounds,
#[error("y size is out of bounds for range")]
#[allow(missing_docs)]
YSizeOutOfBouns,
}
bitflags! {
pub struct GeneratorFlags: u32 {
#[allow(missing_docs)]
const LargeBiomes = 0x1;
#[allow(missing_docs)]
const NoBetaOcean = 0x2;
#[allow(missing_docs)]
const ForceOceanVariants = 0x4;
#[allow(missing_docs)]
const _ = !0;
}
}
#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Scale {
Block = 1,
Quad = 4,
Chunk = 16,
QuadChunk = 64,
HalfRegion = 256,
}
impl Scale {
pub fn scale_coord(&self, num: i32) -> i32 {
num / *self as i32
}
pub fn unscale_coord(&self, num: i32) -> i32 {
num * *self as i32
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct Range {
pub scale: Scale,
pub x: i32,
pub z: i32,
pub size_x: u32,
pub size_z: u32,
pub y: i32,
pub size_y: u32,
}
impl TryFrom<Range> for cubiomes_sys::Range {
type Error = GeneratorError;
fn try_from(value: Range) -> Result<Self, Self::Error> {
Ok(Self {
scale: value.scale as i32,
x: value.x,
z: value.z,
sx: value
.size_x
.try_into()
.map_err(|_| TryFromRangeError::XSizeOutOfBounds)
.and_then(|sx| err_if_zero(sx, TryFromRangeError::XSizeOutOfBounds))?,
sz: value
.size_z
.try_into()
.map_err(|_| TryFromRangeError::ZSizeOutOfBounds)
.and_then(|sz| err_if_zero(sz, TryFromRangeError::YSizeOutOfBouns))?,
y: value.y,
sy: value
.size_y
.try_into()
.map_err(|_| TryFromRangeError::YSizeOutOfBouns)?,
})
}
}
impl Range {
pub fn is_inside(&self, x: i32, z: i32) -> bool {
((self.x <= self.scale.scale_coord(x))
&& (self.scale.scale_coord(x) < (self.x + self.size_x as i32)))
&& ((self.z <= self.scale.scale_coord(z))
&& (self.scale.scale_coord(z) < (self.z + self.size_z as i32)))
}
pub fn global_to_local_coord(&self, x: i32, z: i32) -> Option<(u32, u32)> {
if !self.is_inside(x, z) {
None
} else {
Some((
(self.scale.scale_coord(x) - self.x)
.try_into()
.expect("x should always been in range since we tested it before"),
(self.scale.scale_coord(z) - self.z)
.try_into()
.expect("z should always been in range since we tested it before"),
))
}
}
}
fn err_if_zero(num: i32, err: TryFromRangeError) -> Result<i32, TryFromRangeError> {
if num == 0 {
return Err(err);
}
Ok(num)
}
#[derive(Debug)]
pub struct Generator {
generator: *mut cubiomes_sys::Generator,
}
impl Drop for Generator {
fn drop(&mut self) {
unsafe {
dealloc(self.generator as *mut u8, Layout::new::<Generator>());
}
}
}
impl Generator {
pub fn new(
mc_version: enums::MCVersion,
seed: i64,
dimension: enums::Dimension,
flags: GeneratorFlags,
) -> Self {
unsafe {
let mut generator = Generator::new_without_seed(mc_version, flags);
generator.apply_seed(dimension, seed);
generator
}
}
pub unsafe fn new_without_seed(version: enums::MCVersion, flags: GeneratorFlags) -> Self {
unsafe {
let generator =
alloc(Layout::new::<cubiomes_sys::Generator>()) as *mut cubiomes_sys::Generator;
cubiomes_sys::setupGenerator(generator, version as i32, flags.bits());
Self { generator }
}
}
pub fn apply_seed(&mut self, dimension: enums::Dimension, seed: i64) {
unsafe {
cubiomes_sys::applySeed(
self.generator,
dimension as i32,
transmute::<i64, u64>(seed),
);
}
}
pub fn get_biome_at(&self, x: i32, y: i32, z: i32) -> Result<enums::BiomeID, GeneratorError> {
unsafe {
match cubiomes_sys::getBiomeAt(self.generator, Scale::Block as i32, x, y, z) {
-1 => Err(GeneratorError::GetBiomeAtFailure),
n => FromPrimitive::from_i32(n).ok_or(GeneratorError::BiomeIDOutOfRange(n)),
}
}
}
fn min_cache_size_from_range(&self, range: Range) -> usize {
#[allow(clippy::unwrap_used)]
let raw_range: cubiomes_sys::Range = range.try_into().unwrap();
unsafe {
self.unchecked_min_cache_size(raw_range.scale, raw_range.sx, raw_range.sy, raw_range.sz)
}
}
unsafe fn unchecked_min_cache_size(
&self,
scale: i32,
size_x: i32,
size_y: i32,
size_z: i32,
) -> usize {
unsafe { getMinCacheSize(self.generator, scale, size_x, size_y, size_z) }
}
unsafe fn generate_biomes_to_cache(&self, cache: &mut Cache) -> Result<(), GeneratorError> {
let result_num = cubiomes_sys::genBiomes(
self.generator,
cache.buffer.as_mut_ptr(),
cache.range.try_into()?,
);
if result_num != 0 {
return Err(GeneratorError::GenBiomeToCacheFailure(result_num));
}
cache
.buffer
.set_len(cache.calculate_readable_cache_length());
Ok(())
}
}
impl<'a> Generator {
pub fn new_cache(&'a self, range: Range) -> Cache<'a> {
let cache_size = self.min_cache_size_from_range(range);
let cache = Vec::with_capacity(cache_size);
Cache {
buffer: cache,
range,
generator: self,
}
}
}
#[derive(Clone)]
pub struct Cache<'a> {
buffer: Vec<i32>,
range: Range,
generator: &'a Generator,
}
impl Debug for Cache<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, ":")?;
writeln!(f, "Range: {:?}", &self.range)?;
writeln!(f, "Cache: ")?;
for line in self.buffer.chunks(self.range.size_x as usize) {
writeln!(f, "{:?}", line)?
}
Ok(())
}
}
impl Cache<'_> {
pub fn fill_cache(&mut self) -> Result<(), GeneratorError> {
unsafe { self.generator.generate_biomes_to_cache(self) }
}
pub fn buffer(&self) -> &Vec<i32> {
&self.buffer
}
pub fn range(&self) -> &Range {
&self.range
}
pub fn biome_at(&self, x: u32, y: u32, z: u32) -> Result<enums::BiomeID, GeneratorError> {
let raw_biomeid = *self
.buffer
.get((y * self.range.size_x * self.range.size_z + z * self.range.size_x + x) as usize)
.ok_or(GeneratorError::IndexOutOfBounds)?;
enums::BiomeID::from_i32(raw_biomeid).ok_or(GeneratorError::BiomeIDOutOfRange(raw_biomeid))
}
pub fn move_cache(&mut self, x: i32, y: i32, z: i32) {
let new_range = Range {
x,
y,
z,
..self.range
};
self.range = new_range
}
fn calculate_readable_cache_length(&self) -> usize {
let y_size = match self.range.size_y {
0 => 1,
n => n,
};
(self.range.size_x * self.range.size_z * y_size) as usize
}
}