mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
More Int & Float Providers (#837)
* cloned * feat: Expand float and int provider implementations with comprehensive random distributions - Add ConstantFloatProvider, ClampedNormalFloatProvider, and TrapezoidFloatProvider to NormalFloatProvider enum - Add ConstantIntProvider, BiasedToBottomIntProvider, ClampedIntProvider, ClampedNormalIntProvider, and WeightedListIntProvider to NormalIntProvider enum - Implement ToTokens trait for code generation support across all provider types - Replace external rand dependency with internal RandomImpl trait for consistent randomization - Fix UniformFloatProvider range semantics (max_inclusive → max_exclusive) - Add comprehensive unit tests for all provider implementations - Update block experience drop calculation to use new random provider system * cargo fmt
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct LANBroadcastConfig {
|
||||
pub enabled: bool,
|
||||
// We use an extra `motd` because this only supports one line,
|
||||
// but we use the server `motd` without new lines as the default.
|
||||
pub motd: Option<String>,
|
||||
// Allow users to specify port so that the port is predictable.
|
||||
// There are many reasons why the port might need to be predictable.
|
||||
// One reason is Docker containers, where specific ports need to be allowed.
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct LANBroadcastConfig {
|
||||
pub enabled: bool,
|
||||
// We use an extra `motd` because this only supports one line,
|
||||
// but we use the server `motd` without new lines as the default.
|
||||
pub motd: Option<String>,
|
||||
// Allow users to specify port so that the port is predictable.
|
||||
// There are many reasons why the port might need to be predictable.
|
||||
// One reason is Docker containers, where specific ports need to be allowed.
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,47 @@
|
||||
use crate::random::RandomImpl;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, quote};
|
||||
use serde::Deserialize;
|
||||
use syn::LitFloat;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NormalFloatProvider {
|
||||
#[serde(rename = "minecraft:constant")]
|
||||
Constant(ConstantFloatProvider),
|
||||
#[serde(rename = "minecraft:uniform")]
|
||||
Uniform(UniformFloatProvider),
|
||||
// TODO: Add more...
|
||||
#[serde(rename = "minecraft:clamped_normal")]
|
||||
ClampedNormal(ClampedNormalFloatProvider),
|
||||
#[serde(rename = "minecraft:trapezoid")]
|
||||
Trapezoid(TrapezoidFloatProvider),
|
||||
}
|
||||
|
||||
impl ToTokens for NormalFloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
NormalFloatProvider::Constant(constant) => {
|
||||
tokens.extend(quote! {
|
||||
NormalFloatProvider::Constant(#constant)
|
||||
});
|
||||
}
|
||||
NormalFloatProvider::Uniform(uniform) => {
|
||||
tokens.extend(quote! {
|
||||
NormalFloatProvider::Uniform(#uniform)
|
||||
});
|
||||
}
|
||||
NormalFloatProvider::ClampedNormal(clamped_normal) => {
|
||||
tokens.extend(quote! {
|
||||
NormalFloatProvider::ClampedNormal(#clamped_normal)
|
||||
});
|
||||
}
|
||||
NormalFloatProvider::Trapezoid(trapezoid) => {
|
||||
tokens.extend(quote! {
|
||||
NormalFloatProvider::Trapezoid(#trapezoid)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -15,20 +51,41 @@ pub enum FloatProvider {
|
||||
Constant(f32),
|
||||
}
|
||||
|
||||
impl ToTokens for FloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
FloatProvider::Object(float_provider) => {
|
||||
tokens.extend(quote! {
|
||||
FloatProvider::Object(#float_provider)
|
||||
});
|
||||
}
|
||||
FloatProvider::Constant(f) => tokens.extend(quote! {
|
||||
FloatProvider::Constant(#f)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatProvider {
|
||||
pub fn get_min(&self) -> f32 {
|
||||
match self {
|
||||
FloatProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalFloatProvider::Constant(constant) => constant.get_min(),
|
||||
NormalFloatProvider::Uniform(uniform) => uniform.get_min(),
|
||||
NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get_min(),
|
||||
NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get_min(),
|
||||
},
|
||||
FloatProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> f32 {
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
|
||||
match self {
|
||||
FloatProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalFloatProvider::Uniform(uniform) => uniform.get(),
|
||||
NormalFloatProvider::Constant(constant) => constant.get(random),
|
||||
NormalFloatProvider::Uniform(uniform) => uniform.get(random),
|
||||
NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get(random),
|
||||
NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get(random),
|
||||
},
|
||||
FloatProvider::Constant(i) => *i,
|
||||
}
|
||||
@@ -37,27 +94,310 @@ impl FloatProvider {
|
||||
pub fn get_max(&self) -> f32 {
|
||||
match self {
|
||||
FloatProvider::Object(inv_provider) => match inv_provider {
|
||||
NormalFloatProvider::Constant(constant) => constant.get_max(),
|
||||
NormalFloatProvider::Uniform(uniform) => uniform.get_max(),
|
||||
NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get_max(),
|
||||
NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get_max(),
|
||||
},
|
||||
FloatProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct ConstantFloatProvider {
|
||||
value: f32,
|
||||
}
|
||||
|
||||
impl ToTokens for ConstantFloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let value = LitFloat::new(&self.value.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
ConstantFloatProvider { value: #value }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantFloatProvider {
|
||||
pub fn new(value: f32) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> f32 {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get(&self, _random: &mut impl RandomImpl) -> f32 {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> f32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct UniformFloatProvider {
|
||||
min_inclusive: f32,
|
||||
max_inclusive: f32,
|
||||
max_exclusive: f32,
|
||||
}
|
||||
|
||||
impl ToTokens for UniformFloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let min_inclusive = LitFloat::new(&self.min_inclusive.to_string(), Span::call_site());
|
||||
let max_exclusive = LitFloat::new(&self.max_exclusive.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
UniformFloatProvider { min_inclusive: #min_inclusive, max_exclusive: #max_exclusive }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl UniformFloatProvider {
|
||||
pub fn new(min_inclusive: f32, max_exclusive: f32) -> Self {
|
||||
Self {
|
||||
min_inclusive,
|
||||
max_exclusive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> f32 {
|
||||
self.min_inclusive
|
||||
}
|
||||
pub fn get(&self) -> f32 {
|
||||
rand::random_range(self.min_inclusive..self.max_inclusive)
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
|
||||
// Use the random range in [min_inclusive, max_exclusive)
|
||||
let range = self.max_exclusive - self.min_inclusive;
|
||||
self.min_inclusive + random.next_f32() * range
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> f32 {
|
||||
self.max_inclusive
|
||||
self.max_exclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct ClampedNormalFloatProvider {
|
||||
mean: f32,
|
||||
deviation: f32,
|
||||
min: f32,
|
||||
max: f32,
|
||||
}
|
||||
|
||||
impl ToTokens for ClampedNormalFloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mean = LitFloat::new(&self.mean.to_string(), Span::call_site());
|
||||
let deviation = LitFloat::new(&self.deviation.to_string(), Span::call_site());
|
||||
let min = LitFloat::new(&self.min.to_string(), Span::call_site());
|
||||
let max = LitFloat::new(&self.max.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
ClampedNormalFloatProvider {
|
||||
mean: #mean,
|
||||
deviation: #deviation,
|
||||
min: #min,
|
||||
max: #max
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ClampedNormalFloatProvider {
|
||||
pub fn new(mean: f32, deviation: f32, min: f32, max: f32) -> Self {
|
||||
Self {
|
||||
mean,
|
||||
deviation,
|
||||
min,
|
||||
max,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> f32 {
|
||||
self.min
|
||||
}
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
|
||||
// Generate normal distribution value
|
||||
let gaussian = random.next_gaussian() as f32;
|
||||
let value = self.mean + gaussian * self.deviation;
|
||||
|
||||
// Clamp to min/max range
|
||||
value.clamp(self.min, self.max)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> f32 {
|
||||
self.max
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct TrapezoidFloatProvider {
|
||||
min: f32,
|
||||
max: f32,
|
||||
plateau: f32,
|
||||
}
|
||||
|
||||
impl ToTokens for TrapezoidFloatProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let min = LitFloat::new(&self.min.to_string(), Span::call_site());
|
||||
let max = LitFloat::new(&self.max.to_string(), Span::call_site());
|
||||
let plateau = LitFloat::new(&self.plateau.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
TrapezoidFloatProvider {
|
||||
min: #min,
|
||||
max: #max,
|
||||
plateau: #plateau
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl TrapezoidFloatProvider {
|
||||
pub fn new(min: f32, max: f32, plateau: f32) -> Self {
|
||||
Self { min, max, plateau }
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> f32 {
|
||||
self.min
|
||||
}
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
|
||||
// Trapezoid distribution: flat plateau in the middle, linear ramps on sides
|
||||
let range = self.max - self.min;
|
||||
let plateau_range = range * self.plateau;
|
||||
let ramp_range = (range - plateau_range) * 0.5;
|
||||
|
||||
let random_value = random.next_f32();
|
||||
|
||||
if random_value < 0.5 - self.plateau * 0.5 {
|
||||
// Left ramp: quadratic distribution biased toward plateau
|
||||
let scaled = random_value / (0.5 - self.plateau * 0.5);
|
||||
let sqrt_scaled = scaled.sqrt();
|
||||
self.min + ramp_range * sqrt_scaled
|
||||
} else if random_value > 0.5 + self.plateau * 0.5 {
|
||||
// Right ramp: quadratic distribution biased toward plateau
|
||||
let scaled = (random_value - (0.5 + self.plateau * 0.5)) / (0.5 - self.plateau * 0.5);
|
||||
let sqrt_scaled = (1.0 - scaled).sqrt();
|
||||
self.max - ramp_range * sqrt_scaled
|
||||
} else {
|
||||
// Plateau: uniform distribution
|
||||
let plateau_pos = (random_value - (0.5 - self.plateau * 0.5)) / self.plateau;
|
||||
self.min + ramp_range + plateau_pos * plateau_range
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> f32 {
|
||||
self.max
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::random::{RandomGenerator, get_seed};
|
||||
|
||||
#[test]
|
||||
fn test_constant_float_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = ConstantFloatProvider::new(5.5);
|
||||
|
||||
assert_eq!(provider.get_min(), 5.5);
|
||||
assert_eq!(provider.get_max(), 5.5);
|
||||
assert_eq!(provider.get(&mut random), 5.5);
|
||||
assert_eq!(provider.get(&mut random), 5.5); // Should always return the same value
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniform_float_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = UniformFloatProvider::new(1.0, 5.0);
|
||||
|
||||
assert_eq!(provider.get_min(), 1.0);
|
||||
assert_eq!(provider.get_max(), 5.0);
|
||||
|
||||
// Test that values are within range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1.0..5.0).contains(&value),
|
||||
"Value {} is outside range [1.0, 5.0)",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clamped_normal_float_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = ClampedNormalFloatProvider::new(3.0, 1.0, 1.0, 5.0);
|
||||
|
||||
assert_eq!(provider.get_min(), 1.0);
|
||||
assert_eq!(provider.get_max(), 5.0);
|
||||
|
||||
// Test that values are within range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1.0..=5.0).contains(&value),
|
||||
"Value {} is outside range [1.0, 5.0]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trapezoid_float_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = TrapezoidFloatProvider::new(0.0, 10.0, 0.5);
|
||||
|
||||
assert_eq!(provider.get_min(), 0.0);
|
||||
assert_eq!(provider.get_max(), 10.0);
|
||||
|
||||
// Test that values are within range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(0.0..=10.0).contains(&value),
|
||||
"Value {} is outside range [0.0, 10.0]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_provider_enum_constant() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = FloatProvider::Constant(7.5);
|
||||
|
||||
assert_eq!(provider.get_min(), 7.5);
|
||||
assert_eq!(provider.get_max(), 7.5);
|
||||
assert_eq!(provider.get(&mut random), 7.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_provider_enum_object() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let uniform = UniformFloatProvider::new(2.0, 8.0);
|
||||
let provider = FloatProvider::Object(NormalFloatProvider::Uniform(uniform));
|
||||
|
||||
assert_eq!(provider.get_min(), 2.0);
|
||||
assert_eq!(provider.get_max(), 8.0);
|
||||
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(2.0..8.0).contains(&value),
|
||||
"Value {} is outside range [2.0, 8.0)",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,59 @@
|
||||
use crate::random::RandomImpl;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, quote};
|
||||
use serde::Deserialize;
|
||||
use syn::LitInt;
|
||||
|
||||
use crate::random::{RandomGenerator, RandomImpl};
|
||||
|
||||
use super::pool::{Pool, Weighted};
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NormalIntProvider {
|
||||
#[serde(rename = "minecraft:constant")]
|
||||
Constant(ConstantIntProvider),
|
||||
#[serde(rename = "minecraft:uniform")]
|
||||
Uniform(UniformIntProvider),
|
||||
#[serde(rename = "minecraft:weighted_list")]
|
||||
WeightedList(WeightedListIntProvider),
|
||||
#[serde(rename = "minecraft:biased_to_bottom")]
|
||||
BiasedToBottom(BiasedToBottomIntProvider),
|
||||
#[serde(rename = "minecraft:clamped")]
|
||||
Clamped(ClampedIntProvider),
|
||||
#[serde(rename = "minecraft:clamped_normal")]
|
||||
ClampedNormal(ClampedNormalIntProvider),
|
||||
#[serde(rename = "minecraft:biased_to_bottom")]
|
||||
BiasedToBottom(BiasedToBottomIntProvider), // TODO: Add more...
|
||||
#[serde(rename = "minecraft:weighted_list")]
|
||||
WeightedList(WeightedListIntProvider),
|
||||
}
|
||||
|
||||
impl ToTokens for NormalIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
NormalIntProvider::Constant(constant) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::Constant(#constant)
|
||||
});
|
||||
}
|
||||
NormalIntProvider::Uniform(uniform) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::Uniform(#uniform)
|
||||
});
|
||||
}
|
||||
NormalIntProvider::WeightedList(_) => todo!(),
|
||||
NormalIntProvider::Clamped(_) => todo!(),
|
||||
NormalIntProvider::BiasedToBottom(_biased_to_bottom_int_provider) => todo!(),
|
||||
NormalIntProvider::ClampedNormal(_clamped_int_provider) => todo!(),
|
||||
NormalIntProvider::BiasedToBottom(biased) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::BiasedToBottom(#biased)
|
||||
});
|
||||
}
|
||||
NormalIntProvider::Clamped(clamped) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::Clamped(#clamped)
|
||||
});
|
||||
}
|
||||
NormalIntProvider::ClampedNormal(clamped_normal) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::ClampedNormal(#clamped_normal)
|
||||
});
|
||||
}
|
||||
NormalIntProvider::WeightedList(weighted_list) => {
|
||||
tokens.extend(quote! {
|
||||
NormalIntProvider::WeightedList(#weighted_list)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,24 +84,26 @@ impl IntProvider {
|
||||
pub fn get_min(&self) -> i32 {
|
||||
match self {
|
||||
IntProvider::Object(int_provider) => match int_provider {
|
||||
NormalIntProvider::Constant(constant) => constant.get_min(),
|
||||
NormalIntProvider::Uniform(uniform) => uniform.get_min(),
|
||||
NormalIntProvider::WeightedList(provider) => provider.get_min(),
|
||||
NormalIntProvider::Clamped(provider) => provider.get_min(),
|
||||
NormalIntProvider::BiasedToBottom(provider) => provider.get_min(),
|
||||
NormalIntProvider::ClampedNormal(provider) => provider.get_min(),
|
||||
NormalIntProvider::BiasedToBottom(biased) => biased.get_min(),
|
||||
NormalIntProvider::Clamped(clamped) => clamped.get_min(),
|
||||
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_min(),
|
||||
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_min(),
|
||||
},
|
||||
IntProvider::Constant(i) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
match self {
|
||||
IntProvider::Object(int_provider) => match int_provider {
|
||||
NormalIntProvider::Constant(constant) => constant.get(random),
|
||||
NormalIntProvider::Uniform(uniform) => uniform.get(random),
|
||||
NormalIntProvider::WeightedList(provider) => provider.get(random),
|
||||
NormalIntProvider::Clamped(provider) => provider.get(random),
|
||||
NormalIntProvider::BiasedToBottom(provider) => provider.get(random),
|
||||
NormalIntProvider::ClampedNormal(provider) => provider.get(random),
|
||||
NormalIntProvider::BiasedToBottom(biased) => biased.get(random),
|
||||
NormalIntProvider::Clamped(clamped) => clamped.get(random),
|
||||
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get(random),
|
||||
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get(random),
|
||||
},
|
||||
IntProvider::Constant(i) => *i,
|
||||
}
|
||||
@@ -90,11 +112,12 @@ impl IntProvider {
|
||||
pub fn get_max(&self) -> i32 {
|
||||
match self {
|
||||
IntProvider::Object(int_provider) => match int_provider {
|
||||
NormalIntProvider::Constant(constant) => constant.get_max(),
|
||||
NormalIntProvider::Uniform(uniform) => uniform.get_max(),
|
||||
NormalIntProvider::WeightedList(provider) => provider.get_max(),
|
||||
NormalIntProvider::Clamped(provider) => provider.get_max(),
|
||||
NormalIntProvider::BiasedToBottom(provider) => provider.get_max(),
|
||||
NormalIntProvider::ClampedNormal(provider) => provider.get_max(),
|
||||
NormalIntProvider::BiasedToBottom(biased) => biased.get_max(),
|
||||
NormalIntProvider::Clamped(clamped) => clamped.get_max(),
|
||||
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_max(),
|
||||
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_max(),
|
||||
},
|
||||
IntProvider::Constant(i) => *i,
|
||||
}
|
||||
@@ -102,41 +125,73 @@ impl IntProvider {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct ClampedNormalIntProvider {
|
||||
mean: f32,
|
||||
deviation: f32,
|
||||
min_inclusive: i32,
|
||||
max_inclusive: i32,
|
||||
pub struct ConstantIntProvider {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl ClampedNormalIntProvider {
|
||||
impl ToTokens for ConstantIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let value = LitInt::new(&self.value.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
ConstantIntProvider { value: #value }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantIntProvider {
|
||||
pub fn new(value: i32) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive
|
||||
self.value
|
||||
}
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
(self.mean + random.next_gaussian() as f32 * self.deviation)
|
||||
.clamp(self.min_inclusive as f32, self.max_inclusive as f32) as i32
|
||||
|
||||
pub fn get(&self, _random: &mut impl RandomImpl) -> i32 {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct BiasedToBottomIntProvider {
|
||||
min_inclusive: i32,
|
||||
max_inclusive: i32,
|
||||
pub min_inclusive: i32,
|
||||
pub max_inclusive: i32,
|
||||
}
|
||||
|
||||
impl ToTokens for BiasedToBottomIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let min_inclusive = LitInt::new(&self.min_inclusive.to_string(), Span::call_site());
|
||||
let max_inclusive = LitInt::new(&self.max_inclusive.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
BiasedToBottomIntProvider { min_inclusive: #min_inclusive, max_inclusive: #max_inclusive }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl BiasedToBottomIntProvider {
|
||||
pub fn new(min_inclusive: i32, max_inclusive: i32) -> Self {
|
||||
Self {
|
||||
min_inclusive,
|
||||
max_inclusive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive
|
||||
}
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
// TODO: not sure if this is called first this matches vanilla
|
||||
let first_gen = random.next_bounded_i32(self.max_inclusive - self.min_inclusive + 1) + 1;
|
||||
self.min_inclusive + random.next_bounded_i32(first_gen)
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
// Similar to uniform but biased toward lower values
|
||||
// Uses triangular distribution with mode at min
|
||||
let range = (self.max_inclusive - self.min_inclusive + 1) as f64;
|
||||
let triangular = random.next_triangular(0.0, range);
|
||||
self.min_inclusive + (triangular.abs() as i32).min(self.max_inclusive - self.min_inclusive)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive
|
||||
}
|
||||
@@ -149,47 +204,173 @@ pub struct ClampedIntProvider {
|
||||
max_inclusive: i32,
|
||||
}
|
||||
|
||||
impl ToTokens for ClampedIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let source = &self.source;
|
||||
let min_inclusive = LitInt::new(&self.min_inclusive.to_string(), Span::call_site());
|
||||
let max_inclusive = LitInt::new(&self.max_inclusive.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
ClampedIntProvider {
|
||||
source: Box::new(#source),
|
||||
min_inclusive: #min_inclusive,
|
||||
max_inclusive: #max_inclusive
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ClampedIntProvider {
|
||||
pub fn new(source: IntProvider, min_inclusive: i32, max_inclusive: i32) -> Self {
|
||||
Self {
|
||||
source: Box::new(source),
|
||||
min_inclusive,
|
||||
max_inclusive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive.max(self.source.get_min())
|
||||
}
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
self.source
|
||||
.get(random)
|
||||
.clamp(self.min_inclusive, self.max_inclusive)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive.min(self.source.get_max())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct ClampedNormalIntProvider {
|
||||
mean: f32,
|
||||
deviation: f32,
|
||||
min_inclusive: i32,
|
||||
max_inclusive: i32,
|
||||
}
|
||||
|
||||
impl ToTokens for ClampedNormalIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mean = syn::LitFloat::new(&self.mean.to_string(), Span::call_site());
|
||||
let deviation = syn::LitFloat::new(&self.deviation.to_string(), Span::call_site());
|
||||
let min_inclusive = LitInt::new(&self.min_inclusive.to_string(), Span::call_site());
|
||||
let max_inclusive = LitInt::new(&self.max_inclusive.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
ClampedNormalIntProvider {
|
||||
mean: #mean,
|
||||
deviation: #deviation,
|
||||
min_inclusive: #min_inclusive,
|
||||
max_inclusive: #max_inclusive
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ClampedNormalIntProvider {
|
||||
pub fn new(mean: f32, deviation: f32, min_inclusive: i32, max_inclusive: i32) -> Self {
|
||||
Self {
|
||||
mean,
|
||||
deviation,
|
||||
min_inclusive,
|
||||
max_inclusive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive
|
||||
}
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
// Generate normal distribution value and clamp to range
|
||||
let gaussian = random.next_gaussian() as f32;
|
||||
let value = (self.mean + gaussian * self.deviation).round() as i32;
|
||||
value.clamp(self.min_inclusive, self.max_inclusive)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct WeightedEntry {
|
||||
data: IntProvider,
|
||||
weight: i32,
|
||||
}
|
||||
|
||||
impl ToTokens for WeightedEntry {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let data = &self.data;
|
||||
let weight = LitInt::new(&self.weight.to_string(), Span::call_site());
|
||||
tokens.extend(quote! {
|
||||
WeightedEntry { data: #data, weight: #weight }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct WeightedListIntProvider {
|
||||
distribution: Vec<Weighted<IntProvider>>,
|
||||
distribution: Vec<WeightedEntry>,
|
||||
}
|
||||
|
||||
impl ToTokens for WeightedListIntProvider {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let distribution = &self.distribution;
|
||||
tokens.extend(quote! {
|
||||
WeightedListIntProvider { distribution: vec![#(#distribution),*] }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightedListIntProvider {
|
||||
pub fn new(distribution: Vec<WeightedEntry>) -> Self {
|
||||
Self { distribution }
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
let mut min = i32::MAX;
|
||||
for dist in &self.distribution {
|
||||
let dmin = dist.data.get_min();
|
||||
min = min.min(dmin);
|
||||
}
|
||||
min
|
||||
self.distribution
|
||||
.iter()
|
||||
.map(|entry| entry.data.get_min())
|
||||
.min()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
if let Some(int) = Pool.get(&self.distribution, random) {
|
||||
return int.get(random);
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
if self.distribution.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
0
|
||||
|
||||
// Calculate total weight
|
||||
let total_weight: i32 = self.distribution.iter().map(|entry| entry.weight).sum();
|
||||
|
||||
if total_weight == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Choose random weight
|
||||
let chosen_weight = random.next_bounded_i32(total_weight);
|
||||
let mut current_weight = 0;
|
||||
|
||||
// Find the entry corresponding to the chosen weight
|
||||
for entry in &self.distribution {
|
||||
current_weight += entry.weight;
|
||||
if chosen_weight < current_weight {
|
||||
return entry.data.get(random);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to last entry
|
||||
self.distribution.last().unwrap().data.get(random)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
let mut max = i32::MIN;
|
||||
for dist in &self.distribution {
|
||||
let dmax = dist.data.get_max();
|
||||
max = max.max(dmax);
|
||||
}
|
||||
max
|
||||
self.distribution
|
||||
.iter()
|
||||
.map(|entry| entry.data.get_max())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +392,195 @@ impl ToTokens for UniformIntProvider {
|
||||
}
|
||||
|
||||
impl UniformIntProvider {
|
||||
pub fn new(min_inclusive: i32, max_inclusive: i32) -> Self {
|
||||
Self {
|
||||
min_inclusive,
|
||||
max_inclusive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> i32 {
|
||||
self.min_inclusive
|
||||
}
|
||||
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
|
||||
|
||||
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
|
||||
random.next_inbetween_i32(self.min_inclusive, self.max_inclusive)
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> i32 {
|
||||
self.max_inclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::random::{RandomGenerator, get_seed};
|
||||
|
||||
#[test]
|
||||
fn test_constant_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = ConstantIntProvider::new(42);
|
||||
|
||||
assert_eq!(provider.get_min(), 42);
|
||||
assert_eq!(provider.get_max(), 42);
|
||||
assert_eq!(provider.get(&mut random), 42);
|
||||
assert_eq!(provider.get(&mut random), 42); // Should always return the same value
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniform_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = UniformIntProvider::new(1, 10);
|
||||
|
||||
assert_eq!(provider.get_min(), 1);
|
||||
assert_eq!(provider.get_max(), 10);
|
||||
|
||||
// Test that values are within range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1..=10).contains(&value),
|
||||
"Value {} is outside range [1, 10]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_biased_to_bottom_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = BiasedToBottomIntProvider::new(1, 20);
|
||||
|
||||
assert_eq!(provider.get_min(), 1);
|
||||
assert_eq!(provider.get_max(), 20);
|
||||
|
||||
// Test that values are within range (biased toward lower values)
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1..=20).contains(&value),
|
||||
"Value {} is outside range [1, 20]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clamped_normal_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = ClampedNormalIntProvider::new(5.0, 2.0, 1, 10);
|
||||
|
||||
assert_eq!(provider.get_min(), 1);
|
||||
assert_eq!(provider.get_max(), 10);
|
||||
|
||||
// Test that values are within range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1..=10).contains(&value),
|
||||
"Value {} is outside range [1, 10]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clamped_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let source =
|
||||
IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider::new(1, 100)));
|
||||
let provider = ClampedIntProvider::new(source, 5, 15);
|
||||
|
||||
assert_eq!(provider.get_min(), 5);
|
||||
assert_eq!(provider.get_max(), 15);
|
||||
|
||||
// Test that values are within clamped range
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(5..=15).contains(&value),
|
||||
"Value {} is outside clamped range [5, 15]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weighted_list_int_provider() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
|
||||
let entries = vec![
|
||||
WeightedEntry {
|
||||
data: IntProvider::Constant(1),
|
||||
weight: 10,
|
||||
},
|
||||
WeightedEntry {
|
||||
data: IntProvider::Constant(2),
|
||||
weight: 20,
|
||||
},
|
||||
WeightedEntry {
|
||||
data: IntProvider::Constant(3),
|
||||
weight: 5,
|
||||
},
|
||||
];
|
||||
|
||||
let provider = WeightedListIntProvider::new(entries);
|
||||
|
||||
assert_eq!(provider.get_min(), 1);
|
||||
assert_eq!(provider.get_max(), 3);
|
||||
|
||||
// Test that values are from the weighted list
|
||||
for _ in 0..100 {
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(1..=3).contains(&value),
|
||||
"Value {} is not from the weighted list",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int_provider_enum_constant() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let provider = IntProvider::Constant(25);
|
||||
|
||||
assert_eq!(provider.get_min(), 25);
|
||||
assert_eq!(provider.get_max(), 25);
|
||||
assert_eq!(provider.get(&mut random), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int_provider_enum_object() {
|
||||
let mut random = RandomGenerator::Xoroshiro(
|
||||
crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
|
||||
);
|
||||
let uniform = UniformIntProvider::new(5, 15);
|
||||
let provider = IntProvider::Object(NormalIntProvider::Uniform(uniform));
|
||||
|
||||
assert_eq!(provider.get_min(), 5);
|
||||
assert_eq!(provider.get_max(), 15);
|
||||
|
||||
let value = provider.get(&mut random);
|
||||
assert!(
|
||||
(5..=15).contains(&value),
|
||||
"Value {} is outside range [5, 15]",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,7 @@ use pumpkin_data::{Block, BlockState};
|
||||
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::random::get_seed;
|
||||
use pumpkin_util::random::xoroshiro128::Xoroshiro;
|
||||
use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro};
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use rand::Rng;
|
||||
@@ -188,13 +187,8 @@ pub async fn drop_loot(
|
||||
|
||||
if experience {
|
||||
if let Some(experience) = &block.experience {
|
||||
// TODO: this is bad, this is ugly, this is used :D
|
||||
let amount =
|
||||
experience
|
||||
.experience
|
||||
.get(&mut pumpkin_util::random::RandomGenerator::Xoroshiro(
|
||||
Xoroshiro::from_seed(get_seed()),
|
||||
));
|
||||
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(get_seed()));
|
||||
let amount = experience.experience.get(&mut random);
|
||||
// TODO: Silk touch gives no exp
|
||||
if amount > 0 {
|
||||
ExperienceOrbEntity::spawn(world, pos.to_f64(), amount as u32).await;
|
||||
|
||||
Reference in New Issue
Block a user