mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add Entity spawning on Chunk generation (#1054)
* Support Spawn * Update natural_spawner.rs * fix distance * fix error * fix * use heigh map * Update living.rs * impl get_rough_biome and merge * fix dropper and entity disappear * merge --------- Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ pub struct Biome {
|
||||
features: Vec<Vec<String>>,
|
||||
creature_spawn_probability: Option<f32>,
|
||||
spawners: SpawnGroups,
|
||||
spawn_costs: HashMap<String, SpawnCosts>,
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
@@ -31,17 +32,43 @@ struct SpawnGroups {
|
||||
water_creature: Vec<Spawner>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Hash, PartialEq, Eq)]
|
||||
struct Spawner {
|
||||
r#type: String,
|
||||
minCount: i32,
|
||||
maxCount: i32,
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub fn to_tokens(&self) -> TokenStream {
|
||||
let r#type = &self.r#type;
|
||||
let min_count = &self.minCount;
|
||||
let max_count = &self.maxCount;
|
||||
quote! {
|
||||
Spawner {
|
||||
r#type: #r#type,
|
||||
min_count: #min_count,
|
||||
max_count: #max_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq)]
|
||||
struct SpawnCosts {
|
||||
energy_budget: f64,
|
||||
charge: f64,
|
||||
}
|
||||
|
||||
impl SpawnCosts {
|
||||
pub fn to_tokens(&self) -> TokenStream {
|
||||
let energy_budget = &self.energy_budget;
|
||||
let charge = &self.charge;
|
||||
quote! {
|
||||
SpawnCosts {
|
||||
energy_budget: #energy_budget,
|
||||
charge: #charge,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,6 +244,18 @@ pub(crate) fn build() -> TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let spawn_costs: Vec<_> = biome
|
||||
.spawn_costs
|
||||
.iter()
|
||||
.map(|(name, cost)| {
|
||||
let cost_token = cost.to_tokens();
|
||||
let entity_type = name.strip_prefix("minecraft:").unwrap();
|
||||
quote! {
|
||||
#entity_type => #cost_token
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let temperature_modifier = match temperature_modifier {
|
||||
TemperatureModifier::Frozen => quote! { TemperatureModifier::Frozen },
|
||||
TemperatureModifier::None => quote! { TemperatureModifier::None },
|
||||
@@ -225,17 +264,20 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
variants.extend([quote! {
|
||||
pub const #format_name: Biome = Biome {
|
||||
id: #index,
|
||||
registry_id: #name,
|
||||
weather: Weather::new(
|
||||
#has_precipitation,
|
||||
#temperature,
|
||||
#temperature_modifier,
|
||||
#downfall
|
||||
),
|
||||
features: &[#(&[#(#features),*]),*],
|
||||
creature_spawn_probability: #creature_spawn_probability,
|
||||
spawners: #spawners,
|
||||
id: #index,
|
||||
registry_id: #name,
|
||||
weather: Weather::new(
|
||||
#has_precipitation,
|
||||
#temperature,
|
||||
#temperature_modifier,
|
||||
#downfall
|
||||
),
|
||||
features: &[#(&[#(#features),*]),*],
|
||||
creature_spawn_probability: #creature_spawn_probability,
|
||||
spawners: #spawners,
|
||||
spawn_costs: phf::phf_map! {
|
||||
#(#spawn_costs),*
|
||||
},
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -246,197 +288,223 @@ pub(crate) fn build() -> TokenStream {
|
||||
let overworld_tree = biome_trees.overworld.into_token_stream();
|
||||
let nether_tree = biome_trees.nether.into_token_stream();
|
||||
quote! {
|
||||
use crate::biome::de::Deserialize;
|
||||
use crate::entity_type::EntityType;
|
||||
use crate::tag::Taggable;
|
||||
use crate::tag::RegistryKey;
|
||||
use pumpkin_util::biome::{TemperatureModifier, Weather};
|
||||
use serde::{Deserializer, de};
|
||||
use std::{fmt, hash::{Hasher, Hash}};
|
||||
|
||||
use pumpkin_util::biome::{TemperatureModifier, Weather};
|
||||
use serde::{Deserializer, de};
|
||||
use crate::biome::de::Deserialize;
|
||||
use std::{fmt, hash::{Hasher, Hash}};
|
||||
#[derive(Debug)]
|
||||
pub struct Biome {
|
||||
pub id: u8,
|
||||
pub registry_id: &'static str,
|
||||
pub weather: Weather,
|
||||
// carvers: &'static [&str],
|
||||
pub features: &'static [&'static [&'static str]],
|
||||
pub creature_spawn_probability: f32,
|
||||
pub spawners: SpawnGroups,
|
||||
pub spawn_costs: phf::Map<&'static str, SpawnCosts>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Biome {
|
||||
pub id: u8,
|
||||
pub registry_id: &'static str,
|
||||
pub weather: Weather,
|
||||
// carvers: &'static [&str],
|
||||
pub features: &'static [&'static [&'static str]],
|
||||
pub creature_spawn_probability: f32,
|
||||
pub spawners: SpawnGroups,
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnGroups {
|
||||
pub monster: &'static [Spawner],
|
||||
pub ambient: &'static [Spawner],
|
||||
pub axolotls: &'static [Spawner],
|
||||
pub creature: &'static [Spawner],
|
||||
pub misc: &'static [Spawner],
|
||||
pub underground_water_creature: &'static [Spawner],
|
||||
pub water_ambient: &'static [Spawner],
|
||||
pub water_creature: &'static [Spawner],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Spawner {
|
||||
pub r#type: &'static str,
|
||||
pub min_count: i32,
|
||||
pub max_count: i32,
|
||||
}
|
||||
|
||||
impl PartialEq for Biome {
|
||||
fn eq(&self, other: &Biome) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnGroups {
|
||||
pub monster: &'static [Spawner],
|
||||
pub ambient: &'static [Spawner],
|
||||
pub axolotls: &'static [Spawner],
|
||||
pub creature: &'static [Spawner],
|
||||
pub misc: &'static [Spawner],
|
||||
pub underground_water_creature: &'static [Spawner],
|
||||
pub water_ambient: &'static [Spawner],
|
||||
pub water_creature: &'static [Spawner],
|
||||
impl Eq for Biome {}
|
||||
|
||||
impl Hash for Biome {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Spawner {
|
||||
pub r#type: &'static str,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnCosts {
|
||||
pub energy_budget: f64,
|
||||
pub charge: f64,
|
||||
}
|
||||
|
||||
impl PartialEq for Biome {
|
||||
fn eq(&self, other: &Biome) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for &'static Biome {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct BiomeVisitor;
|
||||
|
||||
impl Eq for Biome {}
|
||||
impl de::Visitor<'_> for BiomeVisitor {
|
||||
type Value = &'static Biome;
|
||||
|
||||
impl Hash for Biome {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for &'static Biome {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct BiomeVisitor;
|
||||
|
||||
impl de::Visitor<'_> for BiomeVisitor {
|
||||
type Value = &'static Biome;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a biome name as a string")
|
||||
}
|
||||
|
||||
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||
self.visit_str(&v)
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
let biome = Biome::from_name(value.strip_prefix("minecraft:").unwrap_or(value));
|
||||
biome.ok_or_else(|| E::unknown_variant(value, &["unknown biome"]))
|
||||
}
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a biome name as a string")
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(BiomeVisitor)
|
||||
}
|
||||
}
|
||||
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||
self.visit_str(&v)
|
||||
}
|
||||
|
||||
impl Biome {
|
||||
#variants
|
||||
|
||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||
match name {
|
||||
#name_to_type
|
||||
_ => None
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
let biome = Biome::from_name(value.strip_prefix("minecraft:").unwrap_or(value));
|
||||
biome.ok_or_else(|| E::unknown_variant(value, &["unknown biome"]))
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_id(id: u8) -> Option<&'static Self> {
|
||||
match id {
|
||||
#id_to_type
|
||||
_ => None
|
||||
}
|
||||
deserializer.deserialize_str(BiomeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Biome {
|
||||
#variants
|
||||
|
||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||
match name {
|
||||
#name_to_type
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct ParameterRange {
|
||||
min: i64,
|
||||
max: i64,
|
||||
pub const fn from_id(id: u8) -> Option<&'static Self> {
|
||||
match id {
|
||||
#id_to_type
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterRange {
|
||||
fn calc_distance(&self, noise: i64) -> i64 {
|
||||
if noise > self.max {
|
||||
noise - self.max
|
||||
} else if noise < self.min {
|
||||
self.min - noise
|
||||
} else {
|
||||
0
|
||||
impl Taggable for Biome {
|
||||
#[inline]
|
||||
fn registry_id(&self) -> u16 {
|
||||
self.id as u16
|
||||
}
|
||||
#[inline]
|
||||
fn tag_key() -> RegistryKey {
|
||||
RegistryKey::WorldgenBiome
|
||||
}
|
||||
#[inline]
|
||||
fn registry_key(&self) -> &str {
|
||||
self.registry_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct ParameterRange {
|
||||
min: i64,
|
||||
max: i64,
|
||||
}
|
||||
|
||||
impl ParameterRange {
|
||||
fn calc_distance(&self, noise: i64) -> i64 {
|
||||
if noise > self.max {
|
||||
noise - self.max
|
||||
} else if noise < self.min {
|
||||
self.min - noise
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BiomeTree {
|
||||
Leaf {
|
||||
parameters: [ParameterRange; 7],
|
||||
biome: &'static Biome,
|
||||
},
|
||||
Branch {
|
||||
parameters: [ParameterRange; 7],
|
||||
nodes: &'static [BiomeTree],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
impl BiomeTree {
|
||||
pub fn get(
|
||||
&'static self,
|
||||
point_list: &[i64; 7],
|
||||
previous_result_node: &mut Option<&'static BiomeTree>,
|
||||
) -> &'static Biome {
|
||||
let result_node = self.get_resulting_node(point_list, *previous_result_node);
|
||||
match result_node {
|
||||
BiomeTree::Leaf { biome, .. } => {
|
||||
*previous_result_node = Some(result_node);
|
||||
biome
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BiomeTree {
|
||||
Leaf {
|
||||
parameters: [ParameterRange; 7],
|
||||
biome: &'static Biome,
|
||||
},
|
||||
Branch {
|
||||
parameters: [ParameterRange; 7],
|
||||
nodes: &'static [BiomeTree],
|
||||
},
|
||||
}
|
||||
fn get_resulting_node(
|
||||
&'static self,
|
||||
point_list: &[i64; 7],
|
||||
previous_result_node: Option<&'static BiomeTree>,
|
||||
) -> &'static BiomeTree {
|
||||
match self {
|
||||
Self::Leaf { .. } => self,
|
||||
Self::Branch { nodes, .. } => {
|
||||
let mut distance = previous_result_node
|
||||
.map(|node| node.get_squared_distance(point_list))
|
||||
.unwrap_or(i64::MAX);
|
||||
let mut best_node = previous_result_node;
|
||||
|
||||
for node in *nodes {
|
||||
let node_distance = node.get_squared_distance(point_list);
|
||||
if distance > node_distance {
|
||||
let node2 = node.get_resulting_node(point_list, best_node);
|
||||
let node2_distance = if node == node2 {
|
||||
node_distance
|
||||
} else {
|
||||
node2.get_squared_distance(point_list)
|
||||
};
|
||||
|
||||
impl BiomeTree {
|
||||
pub fn get(
|
||||
&'static self,
|
||||
point_list: &[i64; 7],
|
||||
previous_result_node: &mut Option<&'static BiomeTree>,
|
||||
) -> &'static Biome {
|
||||
let result_node = self.get_resulting_node(point_list, *previous_result_node);
|
||||
match result_node {
|
||||
BiomeTree::Leaf { biome, .. } => {
|
||||
*previous_result_node = Some(result_node);
|
||||
biome
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_resulting_node(
|
||||
&'static self,
|
||||
point_list: &[i64; 7],
|
||||
previous_result_node: Option<&'static BiomeTree>,
|
||||
) -> &'static BiomeTree {
|
||||
match self {
|
||||
Self::Leaf { .. } => self,
|
||||
Self::Branch { nodes, .. } => {
|
||||
let mut distance = previous_result_node
|
||||
.map(|node| node.get_squared_distance(point_list))
|
||||
.unwrap_or(i64::MAX);
|
||||
let mut best_node = previous_result_node;
|
||||
|
||||
for node in *nodes {
|
||||
let node_distance = node.get_squared_distance(point_list);
|
||||
if distance > node_distance {
|
||||
let node2 = node.get_resulting_node(point_list, best_node);
|
||||
let node2_distance = if node == node2 {
|
||||
node_distance
|
||||
} else {
|
||||
node2.get_squared_distance(point_list)
|
||||
};
|
||||
|
||||
if distance > node2_distance {
|
||||
distance = node2_distance;
|
||||
best_node = Some(node2);
|
||||
}
|
||||
if distance > node2_distance {
|
||||
distance = node2_distance;
|
||||
best_node = Some(node2);
|
||||
}
|
||||
}
|
||||
|
||||
best_node.expect("This should be populated after traversing the tree")
|
||||
}
|
||||
|
||||
best_node.expect("This should be populated after traversing the tree")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_squared_distance(&self, point_list: &[i64; 7]) -> i64 {
|
||||
let parameters = match self {
|
||||
Self::Leaf { parameters, .. } => parameters,
|
||||
Self::Branch { parameters, .. } => parameters,
|
||||
};
|
||||
|
||||
parameters
|
||||
.iter()
|
||||
.zip(point_list)
|
||||
.map(|(bound, value)| {
|
||||
let distance = bound.calc_distance(*value);
|
||||
distance * distance
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
pub const OVERWORLD_BIOME_SOURCE: BiomeTree = #overworld_tree;
|
||||
pub const NETHER_BIOME_SOURCE: BiomeTree = #nether_tree;
|
||||
fn get_squared_distance(&self, point_list: &[i64; 7]) -> i64 {
|
||||
let parameters = match self {
|
||||
Self::Leaf { parameters, .. } => parameters,
|
||||
Self::Branch { parameters, .. } => parameters,
|
||||
};
|
||||
|
||||
parameters
|
||||
.iter()
|
||||
.zip(point_list)
|
||||
.map(|(bound, value)| {
|
||||
let distance = bound.calc_distance(*value);
|
||||
distance * distance
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
pub const OVERWORLD_BIOME_SOURCE: BiomeTree = #overworld_tree;
|
||||
pub const NETHER_BIOME_SOURCE: BiomeTree = #nether_tree;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,13 @@ pub struct EntityType {
|
||||
pub id: u16,
|
||||
pub max_health: Option<f32>,
|
||||
pub attackable: Option<bool>,
|
||||
pub mob: Option<bool>,
|
||||
pub limit_per_chunk: Option<i32>,
|
||||
pub loot_table: Option<LootTableStruct>,
|
||||
pub summonable: bool,
|
||||
pub fire_immune: bool,
|
||||
pub category: MobCategory,
|
||||
pub can_spawn_far_from_player: bool,
|
||||
pub dimension: [f32; 2],
|
||||
pub eye_height: f32,
|
||||
pub spawn_restriction: SpawnRestriction,
|
||||
@@ -36,6 +40,20 @@ pub enum SpawnLocation {
|
||||
Unrestricted,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum MobCategory {
|
||||
MONSTER,
|
||||
CREATURE,
|
||||
AMBIENT,
|
||||
AXOLOTLS,
|
||||
UNDERGROUND_WATER_CREATURE,
|
||||
WATER_CREATURE,
|
||||
WATER_AMBIENT,
|
||||
MISC,
|
||||
}
|
||||
|
||||
pub struct NamedEntityType<'a>(&'a str, &'a EntityType);
|
||||
|
||||
impl ToTokens for NamedEntityType<'_> {
|
||||
@@ -75,9 +93,31 @@ impl ToTokens for NamedEntityType<'_> {
|
||||
heightmap: #spawn_restriction_heightmap,
|
||||
}};
|
||||
|
||||
let spawn_category = match entity.category {
|
||||
MobCategory::MONSTER => quote! { MobCategory::MONSTER },
|
||||
MobCategory::CREATURE => quote! { MobCategory::CREATURE },
|
||||
MobCategory::AMBIENT => quote! { MobCategory::AMBIENT },
|
||||
MobCategory::AXOLOTLS => quote! { MobCategory::AXOLOTLS },
|
||||
MobCategory::UNDERGROUND_WATER_CREATURE => {
|
||||
quote! { MobCategory::UNDERGROUND_WATER_CREATURE }
|
||||
}
|
||||
MobCategory::WATER_CREATURE => quote! { MobCategory::WATER_CREATURE },
|
||||
MobCategory::WATER_AMBIENT => quote! { MobCategory::WATER_AMBIENT },
|
||||
MobCategory::MISC => quote! { MobCategory::MISC },
|
||||
};
|
||||
|
||||
let summonable = entity.summonable;
|
||||
let fire_immune = entity.fire_immune;
|
||||
let eye_height = entity.eye_height;
|
||||
if entity.mob.is_none() && name != "player" {
|
||||
panic!("missing field 'mob', entity name {name}");
|
||||
}
|
||||
if entity.limit_per_chunk.is_none() && name != "player" {
|
||||
panic!("missing field 'mob', entity name {name}");
|
||||
}
|
||||
let mob = entity.mob.unwrap_or(false);
|
||||
let limit_per_chunk = entity.limit_per_chunk.unwrap_or(0);
|
||||
let can_spawn_far_from_player = entity.can_spawn_far_from_player;
|
||||
|
||||
let dimension0 = entity.dimension[0];
|
||||
let dimension1 = entity.dimension[1];
|
||||
@@ -95,8 +135,12 @@ impl ToTokens for NamedEntityType<'_> {
|
||||
id: #id,
|
||||
max_health: #max_health,
|
||||
attackable: #attackable,
|
||||
mob: #mob,
|
||||
limit_per_chunk: #limit_per_chunk,
|
||||
summonable: #summonable,
|
||||
fire_immune: #fire_immune,
|
||||
category: &#spawn_category,
|
||||
can_spawn_far_from_player: #can_spawn_far_from_player,
|
||||
loot_table: #loot_table,
|
||||
dimension: [#dimension0, #dimension1], // Correctly construct the array
|
||||
eye_height: #eye_height,
|
||||
@@ -130,24 +174,28 @@ pub(crate) fn build() -> TokenStream {
|
||||
});
|
||||
|
||||
type_from_raw_id_arms.extend(quote! {
|
||||
#id_lit => Some(Self::#upper_name),
|
||||
#id_lit => Some(&Self::#upper_name),
|
||||
});
|
||||
|
||||
type_from_name.extend(quote! {
|
||||
#name => Some(Self::#upper_name),
|
||||
#name => Some(&Self::#upper_name),
|
||||
});
|
||||
}
|
||||
quote! {
|
||||
use pumpkin_util::loot_table::*;
|
||||
use pumpkin_util::HeightMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct EntityType {
|
||||
pub id: u16,
|
||||
pub max_health: Option<f32>,
|
||||
pub attackable: Option<bool>,
|
||||
pub mob: bool,
|
||||
pub limit_per_chunk: i32,
|
||||
pub summonable: bool,
|
||||
pub fire_immune: bool,
|
||||
pub category: &'static MobCategory,
|
||||
pub can_spawn_far_from_player: bool,
|
||||
pub loot_table: Option<LootTable>,
|
||||
pub dimension: [f32; 2],
|
||||
pub eye_height: f32,
|
||||
@@ -155,31 +203,122 @@ pub(crate) fn build() -> TokenStream {
|
||||
pub resource_name: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SpawnRestriction {
|
||||
location: SpawnLocation,
|
||||
heightmap: HeightMap,
|
||||
impl PartialEq for EntityType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SpawnLocation {
|
||||
InLava,
|
||||
InWater,
|
||||
OnGround,
|
||||
Unrestricted
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnRestriction {
|
||||
pub location: SpawnLocation,
|
||||
pub heightmap: HeightMap,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SpawnLocation {
|
||||
InLava,
|
||||
InWater,
|
||||
OnGround,
|
||||
Unrestricted,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MobCategory {
|
||||
pub id: usize, // mojang don't have this field
|
||||
pub max: i32,
|
||||
pub is_friendly: bool,
|
||||
pub is_persistent: bool,
|
||||
pub despawn_distance: i32,
|
||||
}
|
||||
|
||||
impl PartialEq for MobCategory {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl MobCategory {
|
||||
pub const NO_DESPAWN_DISTANCE: i32 = 32;
|
||||
pub const MONSTER: MobCategory = MobCategory {
|
||||
id: 0,
|
||||
max: 70,
|
||||
is_friendly: false,
|
||||
is_persistent: false,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const CREATURE: MobCategory = MobCategory {
|
||||
id: 1,
|
||||
max: 10,
|
||||
is_friendly: true,
|
||||
is_persistent: true,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const AMBIENT: MobCategory = MobCategory {
|
||||
id: 2,
|
||||
max: 15,
|
||||
is_friendly: true,
|
||||
is_persistent: false,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const AXOLOTLS: MobCategory = MobCategory {
|
||||
id: 3,
|
||||
max: 5,
|
||||
is_friendly: true,
|
||||
is_persistent: false,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const UNDERGROUND_WATER_CREATURE: MobCategory = MobCategory {
|
||||
id: 4,
|
||||
max: 5,
|
||||
is_friendly: true,
|
||||
is_persistent: false,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const WATER_CREATURE: MobCategory = MobCategory {
|
||||
id: 5,
|
||||
max: 5,
|
||||
is_friendly: true,
|
||||
is_persistent: true,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const WATER_AMBIENT: MobCategory = MobCategory {
|
||||
id: 6,
|
||||
max: 20,
|
||||
is_friendly: true,
|
||||
is_persistent: false,
|
||||
despawn_distance: 64,
|
||||
};
|
||||
pub const MISC: MobCategory = MobCategory {
|
||||
id: 7,
|
||||
max: -1,
|
||||
is_friendly: true,
|
||||
is_persistent: true,
|
||||
despawn_distance: 128,
|
||||
};
|
||||
pub const SPAWNING_CATEGORIES: [&'static Self; 8] = [
|
||||
&Self::MONSTER,
|
||||
&Self::CREATURE,
|
||||
&Self::AMBIENT,
|
||||
&Self::AXOLOTLS,
|
||||
&Self::UNDERGROUND_WATER_CREATURE,
|
||||
&Self::WATER_CREATURE,
|
||||
&Self::WATER_AMBIENT,
|
||||
&Self::MISC,
|
||||
];
|
||||
}
|
||||
|
||||
impl EntityType {
|
||||
#consts
|
||||
|
||||
pub const fn from_raw(id: u16) -> Option<Self> {
|
||||
pub const fn from_raw(id: u16) -> Option<&'static Self> {
|
||||
match id {
|
||||
#type_from_raw_id_arms
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||
match name {
|
||||
#type_from_name
|
||||
_ => None
|
||||
|
||||
@@ -15,12 +15,12 @@ pub(crate) fn build() -> TokenStream {
|
||||
for (egg, entity) in &eggs {
|
||||
let entity = entity.to_shouty_snake_case();
|
||||
let entity = format_ident!("{}", entity);
|
||||
names.extend(quote! { #egg => Some(EntityType::#entity), });
|
||||
names.extend(quote! { #egg => Some(&EntityType::#entity), });
|
||||
}
|
||||
quote! {
|
||||
use crate::entity_type::EntityType;
|
||||
|
||||
pub fn entity_from_egg(id: u16) -> Option<EntityType> {
|
||||
pub fn entity_from_egg(id: u16) -> Option<&'static EntityType> {
|
||||
match id {
|
||||
#names
|
||||
_ => None
|
||||
|
||||
@@ -27,6 +27,13 @@ impl<T: Math + Copy> Vector2<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_raw(&self, x: T, y: T) -> Self {
|
||||
Vector2 {
|
||||
x: self.x + x,
|
||||
y: self.y + y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(&self, other: &Vector2<T>) -> Self {
|
||||
Vector2 {
|
||||
x: self.x - other.x,
|
||||
|
||||
@@ -73,11 +73,11 @@ impl DropperBlockEntity {
|
||||
pub async fn get_random_slot(&self) -> Option<MutexGuard<ItemStack>> {
|
||||
// this.unpackLootTable(null);
|
||||
let mut ret = None;
|
||||
let mut j = 0;
|
||||
let mut j = 1;
|
||||
for i in &self.items {
|
||||
let item = i.lock().await;
|
||||
if !item.is_empty() {
|
||||
if rng().random_range(0..=j) == 0 {
|
||||
if rng().random_range(0..j) == 0 {
|
||||
ret = Some(item);
|
||||
}
|
||||
j += 1;
|
||||
|
||||
@@ -303,6 +303,26 @@ impl ChunkSections {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rough_biome_absolute_y(
|
||||
&self,
|
||||
relative_x: usize,
|
||||
y: i32,
|
||||
relative_z: usize,
|
||||
) -> Option<u8> {
|
||||
let y = y - self.min_y;
|
||||
if y < 0 {
|
||||
None
|
||||
} else {
|
||||
let relative_y = y as usize;
|
||||
self.get_noise_biome(
|
||||
relative_y / BlockPalette::SIZE,
|
||||
relative_x >> 2 & 3,
|
||||
relative_y >> 2 & 3,
|
||||
relative_z >> 2 & 3,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the replaced block state ID
|
||||
pub fn set_block_absolute_y(
|
||||
&mut self,
|
||||
@@ -391,6 +411,20 @@ impl ChunkSections {
|
||||
.set(relative_x, relative_y, relative_z, biome_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_noise_biome(
|
||||
&self,
|
||||
index: usize,
|
||||
scale_x: usize,
|
||||
scale_y: usize,
|
||||
scale_z: usize,
|
||||
) -> Option<u8> {
|
||||
debug_assert!(scale_x < BiomePalette::SIZE);
|
||||
debug_assert!(scale_z < BiomePalette::SIZE);
|
||||
self.sections
|
||||
.get(index)
|
||||
.map(|section| section.biomes.get(scale_x, scale_y, scale_z))
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkData {
|
||||
@@ -438,7 +472,7 @@ impl ChunkData {
|
||||
}
|
||||
|
||||
//TODO: Tracking heightmaps update.
|
||||
pub async fn calculate_heightmap(&mut self) -> ChunkHeightmaps {
|
||||
pub fn calculate_heightmap(&mut self) -> ChunkHeightmaps {
|
||||
let highest_non_empty_subchunk = self.get_highest_non_empty_subchunk();
|
||||
let mut heightmaps = ChunkHeightmaps::default();
|
||||
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
use crate::{
|
||||
BlockStateId,
|
||||
block::{RawBlockState, entities::BlockEntity},
|
||||
chunk::{
|
||||
ChunkData, ChunkEntityData, ChunkParsingError, ChunkReadingError,
|
||||
format::{anvil::AnvilChunkFile, linear::LinearFile},
|
||||
io::{Dirtiable, FileIO, LoadedData, file_manager::ChunkFileManager},
|
||||
},
|
||||
dimension::Dimension,
|
||||
generation::{Seed, get_world_gen, implementation::WorldGenerator},
|
||||
tick::{OrderedTick, ScheduledTick, TickPriority},
|
||||
world::BlockRegistryExt,
|
||||
};
|
||||
use dashmap::{DashMap, Entry};
|
||||
use log::trace;
|
||||
use num_traits::Zero;
|
||||
use pumpkin_config::{advanced_config, chunk::ChunkFormat};
|
||||
use pumpkin_data::biome::Biome;
|
||||
use pumpkin_data::{Block, block_properties::has_random_ticks, fluid::Fluid};
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
||||
@@ -24,20 +38,6 @@ use tokio::{
|
||||
};
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
use crate::{
|
||||
BlockStateId,
|
||||
block::{RawBlockState, entities::BlockEntity},
|
||||
chunk::{
|
||||
ChunkData, ChunkEntityData, ChunkParsingError, ChunkReadingError,
|
||||
format::{anvil::AnvilChunkFile, linear::LinearFile},
|
||||
io::{Dirtiable, FileIO, LoadedData, file_manager::ChunkFileManager},
|
||||
},
|
||||
dimension::Dimension,
|
||||
generation::{Seed, get_world_gen, implementation::WorldGenerator},
|
||||
tick::{OrderedTick, ScheduledTick, TickPriority},
|
||||
world::BlockRegistryExt,
|
||||
};
|
||||
|
||||
pub type SyncChunk = Arc<RwLock<ChunkData>>;
|
||||
pub type SyncEntityChunk = Arc<RwLock<ChunkEntityData>>;
|
||||
|
||||
@@ -607,6 +607,21 @@ impl Level {
|
||||
|
||||
RawBlockState(id)
|
||||
}
|
||||
pub async fn get_rough_biome(self: &Arc<Self>, position: &BlockPos) -> &'static Biome {
|
||||
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
|
||||
let chunk = self.get_chunk(chunk_coordinate).await;
|
||||
|
||||
let chunk = chunk.read().await;
|
||||
let Some(id) = chunk.section.get_rough_biome_absolute_y(
|
||||
relative.x as usize,
|
||||
relative.y,
|
||||
relative.z as usize,
|
||||
) else {
|
||||
return &Biome::THE_VOID;
|
||||
};
|
||||
|
||||
Biome::from_id(id).unwrap()
|
||||
}
|
||||
|
||||
pub async fn set_block_state(
|
||||
self: &Arc<Self>,
|
||||
@@ -815,11 +830,12 @@ impl Level {
|
||||
// Deduplicate chunk generation using chunk_generation_locks
|
||||
|
||||
// We are responsible for generating the chunk
|
||||
let generated_chunk = world_gen.generate_chunk(
|
||||
let mut generated_chunk = world_gen.generate_chunk(
|
||||
&self_clone,
|
||||
block_registry.as_ref(),
|
||||
&pos,
|
||||
);
|
||||
generated_chunk.heightmap = generated_chunk.calculate_heightmap();
|
||||
let arc_chunk = Arc::new(RwLock::new(generated_chunk));
|
||||
loaded_chunks.insert(pos, arc_chunk.clone());
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ impl ComposterBlock {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
item_position,
|
||||
EntityType::ITEM,
|
||||
&EntityType::ITEM,
|
||||
false,
|
||||
),
|
||||
ItemStack::new(1, &Item::BONE_MEAL),
|
||||
|
||||
@@ -202,7 +202,7 @@ impl BlockBehaviour for FireBlock {
|
||||
let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
|
||||
if ticks < 0 {
|
||||
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
|
||||
} else if base_entity.entity_type == EntityType::PLAYER {
|
||||
} else if base_entity.entity_type == &EntityType::PLAYER {
|
||||
let rnd_ticks = rand::rng().random_range(1..3);
|
||||
base_entity
|
||||
.fire_ticks
|
||||
|
||||
@@ -21,8 +21,10 @@ impl NetherPortalBlock {
|
||||
async fn get_portal_time(world: &Arc<World>, entity: &dyn EntityBase) -> u32 {
|
||||
let entity_type = entity.get_entity().entity_type;
|
||||
|
||||
match entity_type {
|
||||
EntityType::PLAYER => (world.get_player_by_id(entity.get_entity().entity_id).await)
|
||||
match entity_type.id {
|
||||
id if id == EntityType::PLAYER.id => (world
|
||||
.get_player_by_id(entity.get_entity().entity_id)
|
||||
.await)
|
||||
.map_or(80, |player| match player.gamemode.load() {
|
||||
GameMode::Creative => 0,
|
||||
_ => 80,
|
||||
|
||||
@@ -33,7 +33,7 @@ impl crate::block::BlockBehaviour for PumpkinBlock {
|
||||
Uuid::new_v4(),
|
||||
args.world.clone(),
|
||||
args.position.to_f64(),
|
||||
EntityType::ITEM,
|
||||
&EntityType::ITEM,
|
||||
false,
|
||||
);
|
||||
let item_entity =
|
||||
|
||||
@@ -319,7 +319,7 @@ impl ComparatorBlock {
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|entity| {
|
||||
entity.get_entity().entity_type == EntityType::ITEM_FRAME
|
||||
entity.get_entity().entity_type == &EntityType::ITEM_FRAME
|
||||
&& entity.get_entity().get_horizontal_facing() == facing
|
||||
});
|
||||
if let Some(_itemframe) = itemframes.next() {
|
||||
|
||||
@@ -195,7 +195,7 @@ impl BlockBehaviour for DropperBlock {
|
||||
Uuid::new_v4(),
|
||||
args.world.clone(),
|
||||
position,
|
||||
EntityType::ITEM,
|
||||
&EntityType::ITEM,
|
||||
false,
|
||||
);
|
||||
let rd = rng().random::<f64>() * 0.1 + 0.2;
|
||||
|
||||
@@ -29,7 +29,7 @@ impl TNTBlock {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
location.to_f64(),
|
||||
EntityType::TNT,
|
||||
&EntityType::TNT,
|
||||
false,
|
||||
);
|
||||
let pos = entity.pos.load();
|
||||
@@ -81,7 +81,7 @@ impl BlockBehaviour for TNTBlock {
|
||||
Uuid::new_v4(),
|
||||
args.world.clone(),
|
||||
args.position.to_f64(),
|
||||
EntityType::TNT,
|
||||
&EntityType::TNT,
|
||||
false,
|
||||
);
|
||||
let angle = rand::random::<f64>() * std::f64::consts::TAU;
|
||||
|
||||
@@ -54,7 +54,7 @@ impl DefaultNameArgConsumer for SummonableEntitiesArgumentConsumer {
|
||||
}
|
||||
|
||||
impl<'a> FindArg<'a> for SummonableEntitiesArgumentConsumer {
|
||||
type Data = EntityType;
|
||||
type Data = &'static EntityType;
|
||||
|
||||
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
|
||||
match args.get(name) {
|
||||
|
||||
@@ -19,14 +19,14 @@ pub struct ActiveTargetGoal {
|
||||
track_target_goal: TrackTargetGoal,
|
||||
target: Mutex<Option<Arc<dyn EntityBase>>>,
|
||||
reciprocal_chance: i32,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
target_predicate: TargetPredicate,
|
||||
}
|
||||
|
||||
impl ActiveTargetGoal {
|
||||
pub async fn new<F, Fut>(
|
||||
mob: &MobEntity,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
reciprocal_chance: i32,
|
||||
check_visibility: bool,
|
||||
check_can_navigate: bool,
|
||||
@@ -55,7 +55,7 @@ impl ActiveTargetGoal {
|
||||
#[must_use]
|
||||
pub async fn with_default(
|
||||
mob: &MobEntity,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
check_visibility: bool,
|
||||
) -> Self {
|
||||
let track_target_goal = TrackTargetGoal::with_default(check_visibility);
|
||||
@@ -74,7 +74,7 @@ impl ActiveTargetGoal {
|
||||
async fn find_closest_target(&self, mob: &MobEntity) {
|
||||
let mut target = self.target.lock().await;
|
||||
let world = mob.living_entity.entity.world.read().await;
|
||||
if self.target_type == EntityType::PLAYER {
|
||||
if self.target_type == &EntityType::PLAYER {
|
||||
*target = world
|
||||
.get_closest_player(
|
||||
mob.living_entity.entity.pos.load(),
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct LookAtEntityGoal {
|
||||
look_time: AtomicI32,
|
||||
chance: f64,
|
||||
look_forward: bool,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
target_predicate: TargetPredicate,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl LookAtEntityGoal {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
mob_weak: Weak<dyn Mob>,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
range: f64,
|
||||
chance: f64,
|
||||
look_forward: bool,
|
||||
@@ -46,18 +46,22 @@ impl LookAtEntityGoal {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_default(mob_weak: Weak<dyn Mob>, target_type: EntityType, range: f64) -> Self {
|
||||
pub fn with_default(
|
||||
mob_weak: Weak<dyn Mob>,
|
||||
target_type: &'static EntityType,
|
||||
range: f64,
|
||||
) -> Self {
|
||||
Self::new(mob_weak, target_type, range, 0.02, false)
|
||||
}
|
||||
|
||||
fn create_target_predicate(
|
||||
mob_weak: Weak<dyn Mob>,
|
||||
target_type: EntityType,
|
||||
target_type: &'static EntityType,
|
||||
range: f64,
|
||||
) -> TargetPredicate {
|
||||
let mut target_predicate = TargetPredicate::non_attackable();
|
||||
target_predicate.base_max_distance = range;
|
||||
if target_type == EntityType::PLAYER {
|
||||
if target_type == &EntityType::PLAYER {
|
||||
target_predicate.set_predicate(move |living_entity, _world| {
|
||||
let mob_weak = mob_weak.clone();
|
||||
async move {
|
||||
@@ -92,7 +96,7 @@ impl Goal for LookAtEntityGoal {
|
||||
drop(mob_target);
|
||||
|
||||
let world = mob_entity.living_entity.entity.world.read().await;
|
||||
if self.target_type == EntityType::PLAYER {
|
||||
if self.target_type == &EntityType::PLAYER {
|
||||
*target = world
|
||||
.get_closest_player(mob_entity.living_entity.entity.pos.load(), self.range)
|
||||
.await
|
||||
|
||||
@@ -38,7 +38,7 @@ impl ExperienceOrbEntity {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
position,
|
||||
EntityType::EXPERIENCE_ORB,
|
||||
&EntityType::EXPERIENCE_ORB,
|
||||
false,
|
||||
);
|
||||
let orb = Arc::new(Self::new(entity, i));
|
||||
|
||||
@@ -258,7 +258,7 @@ impl LivingEntity {
|
||||
)
|
||||
.await;
|
||||
let params = LootContextParameters {
|
||||
killed_by_player: cause.map(|c| c.get_entity().entity_type == EntityType::PLAYER),
|
||||
killed_by_player: cause.map(|c| c.get_entity().entity_type == &EntityType::PLAYER),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -267,7 +267,7 @@ impl LivingEntity {
|
||||
|
||||
let level_info = world.level_info.read().await;
|
||||
let game_rules = &level_info.game_rules;
|
||||
if self.entity.entity_type == EntityType::PLAYER && game_rules.show_death_messages {
|
||||
if self.entity.entity_type == &EntityType::PLAYER && game_rules.show_death_messages {
|
||||
//TODO: KillCredit
|
||||
let death_message = if let Some(death_message_type) = damage_type.death_message_type
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ impl Zombie {
|
||||
8,
|
||||
Arc::new(LookAtEntityGoal::with_default(
|
||||
mob_weak,
|
||||
EntityType::PLAYER,
|
||||
&EntityType::PLAYER,
|
||||
8.0,
|
||||
)),
|
||||
)
|
||||
@@ -58,7 +58,7 @@ impl Zombie {
|
||||
.add_goal(
|
||||
2,
|
||||
Arc::new(
|
||||
ActiveTargetGoal::with_default(&mob_arc.mob_entity, EntityType::PLAYER, true)
|
||||
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, true)
|
||||
.await,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -219,7 +219,7 @@ pub struct Entity {
|
||||
/// A persistent, unique identifier for the entity
|
||||
pub entity_uuid: uuid::Uuid,
|
||||
/// The type of entity (e.g., player, zombie, item)
|
||||
pub entity_type: EntityType,
|
||||
pub entity_type: &'static EntityType,
|
||||
/// The world in which the entity exists.
|
||||
pub world: Arc<RwLock<Arc<World>>>,
|
||||
/// The entity's current position in the world
|
||||
@@ -286,7 +286,7 @@ impl Entity {
|
||||
entity_uuid: uuid::Uuid,
|
||||
world: Arc<World>,
|
||||
position: Vector3<f64>,
|
||||
entity_type: EntityType,
|
||||
entity_type: &'static EntityType,
|
||||
invulnerable: bool,
|
||||
) -> Self {
|
||||
let floor_x = position.x.floor() as i32;
|
||||
@@ -305,7 +305,10 @@ impl Entity {
|
||||
on_ground: AtomicBool::new(false),
|
||||
pos: AtomicCell::new(position),
|
||||
block_pos: AtomicCell::new(BlockPos(Vector3::new(floor_x, floor_y, floor_z))),
|
||||
chunk_pos: AtomicCell::new(Vector2::new(floor_x, floor_z)),
|
||||
chunk_pos: AtomicCell::new(Vector2::new(
|
||||
get_section_cord(floor_x),
|
||||
get_section_cord(floor_z),
|
||||
)),
|
||||
sneaking: AtomicBool::new(false),
|
||||
world: Arc::new(RwLock::new(world)),
|
||||
sprinting: AtomicBool::new(false),
|
||||
@@ -436,7 +439,7 @@ impl Entity {
|
||||
}
|
||||
|
||||
fn default_portal_cooldown(&self) -> u32 {
|
||||
if self.entity_type == EntityType::PLAYER {
|
||||
if self.entity_type == &EntityType::PLAYER {
|
||||
10
|
||||
} else {
|
||||
300
|
||||
|
||||
@@ -302,7 +302,7 @@ impl Player {
|
||||
player_uuid,
|
||||
world,
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
EntityType::PLAYER,
|
||||
&EntityType::PLAYER,
|
||||
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
|
||||
));
|
||||
|
||||
@@ -1452,7 +1452,7 @@ impl Player {
|
||||
Uuid::new_v4(),
|
||||
self.world().await,
|
||||
item_pos,
|
||||
EntityType::ITEM,
|
||||
&EntityType::ITEM,
|
||||
false,
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub async fn from_type(
|
||||
entity_type: EntityType,
|
||||
entity_type: &'static EntityType,
|
||||
position: Vector3<f64>,
|
||||
world: &Arc<World>,
|
||||
uuid: Uuid,
|
||||
@@ -18,9 +18,9 @@ pub async fn from_type(
|
||||
let entity = Entity::new(uuid, world.clone(), position, entity_type, false);
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
let mob: Arc<dyn EntityBase> = match entity_type {
|
||||
EntityType::ZOMBIE => Zombie::make(entity).await,
|
||||
EntityType::PAINTING => Arc::new(PaintingEntity::new(entity)),
|
||||
let mob: Arc<dyn EntityBase> = match entity_type.id {
|
||||
id if id == EntityType::ZOMBIE.id => Zombie::make(entity).await,
|
||||
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),
|
||||
// TODO
|
||||
_ => Arc::new(entity), // Fallback Entity
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ impl ItemBehaviour for EggItem {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
position,
|
||||
EntityType::EGG,
|
||||
&EntityType::EGG,
|
||||
false,
|
||||
);
|
||||
let egg = ThrownItemEntity::new(entity, &player.living_entity.entity);
|
||||
|
||||
@@ -85,7 +85,7 @@ impl ItemBehaviour for HoeItem {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
location,
|
||||
EntityType::SNOWBALL,
|
||||
&EntityType::SNOWBALL,
|
||||
false,
|
||||
);
|
||||
// TODO: Merge stacks together
|
||||
|
||||
@@ -20,14 +20,14 @@ use uuid::Uuid;
|
||||
pub struct MinecartItem;
|
||||
|
||||
impl MinecartItem {
|
||||
fn item_to_entity(item: &Item) -> EntityType {
|
||||
fn item_to_entity(item: &Item) -> &'static EntityType {
|
||||
match item.id {
|
||||
val if val == Item::MINECART.id => EntityType::MINECART,
|
||||
val if val == Item::TNT_MINECART.id => EntityType::TNT_MINECART,
|
||||
val if val == Item::CHEST_MINECART.id => EntityType::CHEST_MINECART,
|
||||
val if val == Item::HOPPER_MINECART.id => EntityType::HOPPER_MINECART,
|
||||
val if val == Item::FURNACE_MINECART.id => EntityType::FURNACE_MINECART,
|
||||
val if val == Item::COMMAND_BLOCK_MINECART.id => EntityType::COMMAND_BLOCK_MINECART,
|
||||
val if val == Item::MINECART.id => &EntityType::MINECART,
|
||||
val if val == Item::TNT_MINECART.id => &EntityType::TNT_MINECART,
|
||||
val if val == Item::CHEST_MINECART.id => &EntityType::CHEST_MINECART,
|
||||
val if val == Item::HOPPER_MINECART.id => &EntityType::HOPPER_MINECART,
|
||||
val if val == Item::FURNACE_MINECART.id => &EntityType::FURNACE_MINECART,
|
||||
val if val == Item::COMMAND_BLOCK_MINECART.id => &EntityType::COMMAND_BLOCK_MINECART,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl ItemBehaviour for SnowBallItem {
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
position,
|
||||
EntityType::SNOWBALL,
|
||||
&EntityType::SNOWBALL,
|
||||
false,
|
||||
);
|
||||
let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity);
|
||||
|
||||
@@ -1709,7 +1709,7 @@ impl JavaClient {
|
||||
async fn spawn_entity_from_egg(
|
||||
&self,
|
||||
player: &Player,
|
||||
entity_type: EntityType,
|
||||
entity_type: &'static EntityType,
|
||||
location: BlockPos,
|
||||
face: BlockDirection,
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::sync::Weak;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
@@ -37,6 +38,7 @@ use bytes::BufMut;
|
||||
use explosion::Explosion;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::entity::MobCategory;
|
||||
use pumpkin_data::fluid::{Falling, FluidProperties};
|
||||
use pumpkin_data::{
|
||||
Block,
|
||||
@@ -115,6 +117,7 @@ use pumpkin_world::{
|
||||
};
|
||||
use pumpkin_world::{level::Level, tick::TickPriority};
|
||||
use pumpkin_world::{world::BlockFlags, world_info::LevelData};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::{Rng, rng};
|
||||
use scoreboard::Scoreboard;
|
||||
use serde::Serialize;
|
||||
@@ -125,10 +128,13 @@ use tokio::sync::RwLock;
|
||||
pub mod border;
|
||||
pub mod bossbar;
|
||||
pub mod custom_bossbar;
|
||||
pub mod natural_spawner;
|
||||
pub mod scoreboard;
|
||||
pub mod weather;
|
||||
|
||||
use crate::world::natural_spawner::{SpawnState, spawn_for_chunk};
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_world::chunk::ChunkHeightmapType::MotionBlocking;
|
||||
use pumpkin_world::generation::settings::GenerationSettings;
|
||||
use uuid::Uuid;
|
||||
use weather::Weather;
|
||||
@@ -176,6 +182,7 @@ pub struct World {
|
||||
/// The type of dimension the world is in.
|
||||
pub dimension_type: VanillaDimensionType,
|
||||
pub sea_level: i32,
|
||||
pub min_y: i32,
|
||||
/// The world's weather, including rain and thunder levels.
|
||||
pub weather: Mutex<Weather>,
|
||||
/// Block Behaviour
|
||||
@@ -221,6 +228,7 @@ impl World {
|
||||
weather: Mutex::new(Weather::new()),
|
||||
block_registry,
|
||||
sea_level: generation_settings.sea_level,
|
||||
min_y: i32::from(generation_settings.shape.min_y),
|
||||
synced_block_event_queue: Mutex::new(Vec::new()),
|
||||
unsent_block_changes: Mutex::new(HashMap::new()),
|
||||
server,
|
||||
@@ -683,12 +691,141 @@ impl World {
|
||||
}
|
||||
} */
|
||||
|
||||
let spawn_entity_clock_start = tokio::time::Instant::now();
|
||||
|
||||
let mut spawning_chunks_map = HashMap::new();
|
||||
// TODO use FixedPlayerDistanceChunkTracker
|
||||
|
||||
for i in self.players.read().await.values() {
|
||||
let center = i.living_entity.entity.chunk_pos.load();
|
||||
for dx in -8i32..=8 {
|
||||
for dy in -8i32..=8 {
|
||||
// if dx.abs() <= 2 || dy.abs() <= 2 || dx.abs() >= 6 || dy.abs() >= 6 { // this is only for debug, spawning runs too slow
|
||||
// continue;
|
||||
// }
|
||||
let chunk_pos = center.add_raw(dx, dy);
|
||||
if let Some(chunk) = self.level.try_get_chunk(&chunk_pos) {
|
||||
spawning_chunks_map
|
||||
.entry(chunk_pos)
|
||||
.or_insert(chunk.value().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut spawning_chunks = Vec::with_capacity(spawning_chunks_map.len());
|
||||
for i in spawning_chunks_map {
|
||||
spawning_chunks.push(i);
|
||||
}
|
||||
|
||||
let get_chunks_clock = spawn_entity_clock_start.elapsed();
|
||||
// log::debug!("spawning chunks size {}", spawning_chunks.len());
|
||||
|
||||
let mut spawn_state =
|
||||
SpawnState::new(spawning_chunks.len() as i32, &self.entities, self).await; // TODO store it
|
||||
|
||||
// TODO gamerule this.spawnEnemies || this.spawnFriendlies
|
||||
let spawn_passives = self.level_time.lock().await.time_of_day % 400 == 0;
|
||||
let spawn_list: Vec<&'static MobCategory> =
|
||||
natural_spawner::get_filtered_spawning_categories(
|
||||
&spawn_state,
|
||||
true,
|
||||
true,
|
||||
spawn_passives,
|
||||
);
|
||||
|
||||
// log::debug!("spawning list size {}", spawn_list.len());
|
||||
log::debug!("spawning counter {:?}", spawn_state.mob_category_counts);
|
||||
|
||||
spawning_chunks.shuffle(&mut rng());
|
||||
|
||||
// TODO i think it can be multithread
|
||||
for (pos, chunk) in &spawning_chunks {
|
||||
self.tick_spawning_chunk(pos, chunk, &spawn_list, &mut spawn_state)
|
||||
.await;
|
||||
}
|
||||
log::debug!(
|
||||
"Spawning entity took {:?}, getting chunks {:?}, spawning chunks: {}, avg {:?} per chunk",
|
||||
spawn_entity_clock_start.elapsed(),
|
||||
get_chunks_clock,
|
||||
spawning_chunks.len(),
|
||||
spawn_entity_clock_start
|
||||
.elapsed()
|
||||
.checked_div(spawning_chunks.len() as u32)
|
||||
.unwrap_or(Duration::new(0, 0))
|
||||
);
|
||||
|
||||
for block_entity in tick_data.block_entities {
|
||||
let world: Arc<dyn SimpleWorld> = self.clone();
|
||||
block_entity.tick(world).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn tick_spawning_chunk(
|
||||
self: &Arc<Self>,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
spawn_list: &Vec<&'static MobCategory>,
|
||||
spawn_state: &mut SpawnState,
|
||||
) {
|
||||
// this.level.tickThunder(chunk);
|
||||
//TODO check in simulation distance
|
||||
if self.weather.lock().await.raining
|
||||
&& self.weather.lock().await.thundering
|
||||
&& rng().random_range(0..100_000) == 0
|
||||
{
|
||||
let rand_value = rng().random::<i32>() >> 2;
|
||||
let delta = Vector3::new(rand_value & 15, rand_value >> 16 & 15, rand_value >> 8 & 15);
|
||||
let random_pos = Vector3::new(
|
||||
chunk_pos.x << 4,
|
||||
chunk.read().await.heightmap.get_height(
|
||||
MotionBlocking,
|
||||
chunk_pos.x << 4,
|
||||
chunk_pos.y << 4,
|
||||
self.min_y,
|
||||
),
|
||||
chunk_pos.y << 4,
|
||||
)
|
||||
.add(&delta);
|
||||
// TODO this.getBrightness(LightLayer.SKY, blockPos) >= 15;
|
||||
// TODO heightmap
|
||||
|
||||
// TODO findLightningRod(blockPos)
|
||||
// TODO encapsulatingFullBlocks
|
||||
if true {
|
||||
// TODO biome.getPrecipitationAt(pos, this.getSeaLevel()) == Biome.Precipitation.RAIN
|
||||
// TODO this.getCurrentDifficultyAt(blockPos);
|
||||
if rng().random::<f32>() < 0.0675
|
||||
&& self.get_block(&random_pos.to_block_pos().down()).await
|
||||
!= &Block::LIGHTNING_ROD
|
||||
{
|
||||
let entity = Entity::new(
|
||||
Uuid::new_v4(),
|
||||
self.clone(),
|
||||
random_pos.to_f64(),
|
||||
&EntityType::SKELETON_HORSE,
|
||||
false,
|
||||
);
|
||||
self.spawn_entity(Arc::new(entity)).await;
|
||||
}
|
||||
let entity = Entity::new(
|
||||
Uuid::new_v4(),
|
||||
self.clone(),
|
||||
random_pos.to_f64().add_raw(0.5, 0., 0.5),
|
||||
&EntityType::LIGHTNING_BOLT,
|
||||
false,
|
||||
);
|
||||
self.spawn_entity(Arc::new(entity)).await;
|
||||
}
|
||||
}
|
||||
|
||||
if spawn_list.is_empty() {
|
||||
return;
|
||||
}
|
||||
// TODO this.level.canSpawnEntitiesInChunk(chunkPos)
|
||||
spawn_for_chunk(self, chunk_pos, chunk, spawn_state, spawn_list).await;
|
||||
}
|
||||
|
||||
pub fn generation_settings(&self) -> &GenerationSettings {
|
||||
// TODO: this is bad
|
||||
match self.dimension_type {
|
||||
@@ -1768,7 +1905,7 @@ impl World {
|
||||
&self,
|
||||
pos: Vector3<f64>,
|
||||
radius: f64,
|
||||
entity_types: Option<&[EntityType]>,
|
||||
entity_types: Option<&[&'static EntityType]>,
|
||||
) -> Option<Arc<dyn EntityBase>> {
|
||||
// Get regular entities
|
||||
let entities = self.get_nearby_entities(pos, radius).await;
|
||||
@@ -2158,7 +2295,7 @@ impl World {
|
||||
f64::from(pos.0.z) + 0.5 + rand::rng().random_range(-0.25..0.25),
|
||||
);
|
||||
|
||||
let entity = Entity::new(Uuid::new_v4(), self.clone(), pos, EntityType::ITEM, false);
|
||||
let entity = Entity::new(Uuid::new_v4(), self.clone(), pos, &EntityType::ITEM, false);
|
||||
let item_entity = Arc::new(ItemEntity::new(entity, stack).await);
|
||||
self.spawn_entity(item_entity).await;
|
||||
}
|
||||
@@ -2208,7 +2345,7 @@ impl World {
|
||||
Uuid::new_v4(),
|
||||
self.clone(),
|
||||
Vector3::new(x, y, z),
|
||||
EntityType::ITEM,
|
||||
&EntityType::ITEM,
|
||||
false,
|
||||
);
|
||||
let entity = Arc::new(ItemEntity::new_with_velocity(entity, item, velocity, 10).await);
|
||||
|
||||
563
pumpkin/src/world/natural_spawner.rs
Normal file
563
pumpkin/src/world/natural_spawner.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::r#type::from_type;
|
||||
use crate::world::World;
|
||||
use pumpkin_data::biome::Spawner;
|
||||
use pumpkin_data::entity::{EntityType, MobCategory, SpawnLocation};
|
||||
use pumpkin_data::tag::Block::MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE;
|
||||
use pumpkin_data::tag::Fluid::{MINECRAFT_LAVA, MINECRAFT_WATER};
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS;
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::math::get_section_cord;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::chunk::{ChunkData, ChunkHeightmapType};
|
||||
use rand::seq::IndexedRandom;
|
||||
use rand::{Rng, rng};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
const MAGIC_NUMBER: i32 = 17 * 17;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MobCounts([i32; 8]);
|
||||
|
||||
impl MobCounts {
|
||||
#[inline]
|
||||
const fn add(&mut self, category: &'static MobCategory) {
|
||||
self.0[category.id] += 1;
|
||||
}
|
||||
#[inline]
|
||||
const fn can_spawn(&self, category: &'static MobCategory) -> bool {
|
||||
self.0[category.id] < category.max
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalMobCapCalculator {
|
||||
world: Arc<World>,
|
||||
player_mob_counts: HashMap<i32, MobCounts>,
|
||||
players_near_chunk: HashMap<Vector2<i32>, Vec<i32>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for LocalMobCapCalculator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("LocalMobCapCalculator")
|
||||
.field("world", &"<skipped>")
|
||||
.field("player_mob_counts", &self.player_mob_counts)
|
||||
.field("players_near_chunk", &self.players_near_chunk)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalMobCapCalculator {
|
||||
pub fn new(world: &Arc<World>) -> Self {
|
||||
Self {
|
||||
world: world.clone(), // can anybody get rid of this clone?
|
||||
player_mob_counts: HashMap::new(),
|
||||
players_near_chunk: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn calc_distance(chunk_pos: Vector2<i32>, player_pos: &Vector3<f64>) -> f64 {
|
||||
let dx = ((chunk_pos.x << 4) + 8) as f64 - player_pos.x;
|
||||
let dy = ((chunk_pos.y << 4) + 8) as f64 - player_pos.z;
|
||||
dx * dx + dy * dy
|
||||
}
|
||||
|
||||
async fn get_players_near<'b>(
|
||||
players_near_chunk: &'b mut HashMap<Vector2<i32>, Vec<i32>>,
|
||||
world: &Arc<World>,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
) -> &'b Vec<i32> {
|
||||
match players_near_chunk.entry(*chunk_pos) {
|
||||
Entry::Occupied(value) => {
|
||||
// debug!("chunk {chunk_pos:?} near player {:?}", value.get());
|
||||
value.into_mut()
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
let mut players = Vec::new();
|
||||
for (_uuid, player) in world.players.read().await.iter() {
|
||||
if player.gamemode.load() == GameMode::Spectator {
|
||||
continue;
|
||||
}
|
||||
if Self::calc_distance(*chunk_pos, &player.position()) < 16384. {
|
||||
players.push(player.entity_id());
|
||||
}
|
||||
}
|
||||
// debug!("chunk {chunk_pos:?} near player {:?}", players);
|
||||
entry.insert(players)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn add_mob(&mut self, chunk_pos: &Vector2<i32>, category: &'static MobCategory) {
|
||||
let players =
|
||||
Self::get_players_near(&mut self.players_near_chunk, &self.world, chunk_pos).await;
|
||||
for player in players {
|
||||
self.player_mob_counts
|
||||
.entry(*player)
|
||||
.or_default()
|
||||
.add(category);
|
||||
}
|
||||
// debug!("player_mob_counts {:?}", self.player_mob_counts);
|
||||
// debug!("players_near_chunk {:?}", self.players_near_chunk);
|
||||
// debug!("chunk_pos {:?}", chunk_pos);
|
||||
// debug!("players {:?}", players);
|
||||
}
|
||||
pub async fn can_spawn(
|
||||
&mut self,
|
||||
category: &'static MobCategory,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
) -> bool {
|
||||
let players =
|
||||
Self::get_players_near(&mut self.players_near_chunk, &self.world, chunk_pos).await;
|
||||
for player in players {
|
||||
if let Some(count) = self.player_mob_counts.get(player) {
|
||||
if count.can_spawn(category) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PointCharge(Vector3<f64>, f64); // pos charge
|
||||
|
||||
impl PointCharge {
|
||||
fn get_potential_change(&self, pos: &BlockPos) -> f64 {
|
||||
let dst = self.0.sub(&pos.to_f64()).length();
|
||||
self.1 / dst
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct PotentialCalculator(Vec<PointCharge>);
|
||||
|
||||
impl PotentialCalculator {
|
||||
pub fn add_charge(&mut self, pos: &BlockPos, charge: f64) {
|
||||
if charge != 0. {
|
||||
self.0.push(PointCharge(pos.to_f64(), charge));
|
||||
}
|
||||
}
|
||||
pub fn get_potential_energy_change(&self, pos: &BlockPos, charge: f64) -> f64 {
|
||||
if charge == 0. {
|
||||
return 0.;
|
||||
}
|
||||
let mut sum: f64 = 0.;
|
||||
for i in &self.0 {
|
||||
sum += i.get_potential_change(pos);
|
||||
}
|
||||
sum * charge
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpawnState {
|
||||
spawnable_chunk_count: i32,
|
||||
pub mob_category_counts: MobCounts,
|
||||
spawn_potential: PotentialCalculator,
|
||||
local_mob_cap_calculator: LocalMobCapCalculator,
|
||||
// unmodifiable_mob_category_counts: MobCounts, seems only for debug
|
||||
last_checked_pos: BlockPos,
|
||||
last_checked_type: &'static EntityType,
|
||||
last_charge: f64,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SpawnState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("SpawnState")
|
||||
.field("spawnable_chunk_count", &self.spawnable_chunk_count)
|
||||
.field("mob_category_counts", &self.mob_category_counts)
|
||||
.field("spawn_potential", &self.spawn_potential)
|
||||
.field("local_mob_cap_calculator", &self.local_mob_cap_calculator)
|
||||
.field("last_checked_pos", &self.last_checked_pos)
|
||||
.field("last_checked_type", &self.last_checked_type.resource_name)
|
||||
.field("last_charge", &self.last_charge)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SpawnState {
|
||||
pub async fn new(
|
||||
chunk_count: i32,
|
||||
entities: &Arc<RwLock<HashMap<Uuid, Arc<dyn EntityBase>>>>,
|
||||
world: &Arc<World>,
|
||||
) -> Self {
|
||||
let mut potential = PotentialCalculator::default();
|
||||
let mut local_mob_cap = LocalMobCapCalculator::new(world);
|
||||
let mut counter = MobCounts::default();
|
||||
for entity in entities.read().await.values() {
|
||||
let entity_type = &entity.get_entity().entity_type;
|
||||
#[allow(clippy::overly_complex_bool_expr)]
|
||||
if entity_type.mob && false || entity_type.category == &MobCategory::MISC {
|
||||
// TODO (mob.isPersistenceRequired() || mob.requiresCustomPersistence())
|
||||
continue;
|
||||
}
|
||||
let entity_pos = &entity.get_entity().block_pos.load();
|
||||
let biome = world.level.get_rough_biome(entity_pos).await;
|
||||
if let Some(cost) = biome.spawn_costs.get(entity_type.resource_name) {
|
||||
potential.add_charge(entity_pos, cost.charge);
|
||||
}
|
||||
if entity_type.mob {
|
||||
local_mob_cap
|
||||
.add_mob(&entity.get_entity().chunk_pos.load(), entity_type.category)
|
||||
.await;
|
||||
}
|
||||
counter.add(entity_type.category);
|
||||
}
|
||||
Self {
|
||||
spawnable_chunk_count: chunk_count,
|
||||
mob_category_counts: counter,
|
||||
spawn_potential: potential,
|
||||
local_mob_cap_calculator: local_mob_cap,
|
||||
// unmodifiable_mob_category_counts: counter,
|
||||
last_checked_pos: BlockPos::new(i32::MAX, i32::MAX, i32::MAX),
|
||||
last_checked_type: &EntityType::PLAYER,
|
||||
last_charge: 0.,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn can_spawn_for_category_global(&self, category: &'static MobCategory) -> bool {
|
||||
self.mob_category_counts.0[category.id]
|
||||
< category.max * self.spawnable_chunk_count / MAGIC_NUMBER
|
||||
}
|
||||
async fn can_spawn_for_category_local(
|
||||
&mut self,
|
||||
category: &'static MobCategory,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
) -> bool {
|
||||
self.local_mob_cap_calculator
|
||||
.can_spawn(category, chunk_pos)
|
||||
.await
|
||||
}
|
||||
async fn can_spawn(
|
||||
&mut self,
|
||||
entity_type: &'static EntityType,
|
||||
pos: &BlockPos,
|
||||
world: &Arc<World>,
|
||||
) -> bool {
|
||||
self.last_checked_pos = *pos;
|
||||
self.last_checked_type = entity_type;
|
||||
// TODO get biome
|
||||
let biome = world.level.get_rough_biome(pos).await;
|
||||
if let Some(cost) = biome.spawn_costs.get(entity_type.resource_name) {
|
||||
self.last_charge = cost.charge;
|
||||
self.spawn_potential
|
||||
.get_potential_energy_change(pos, cost.charge)
|
||||
<= cost.energy_budget
|
||||
} else {
|
||||
self.last_charge = 0.;
|
||||
true
|
||||
}
|
||||
}
|
||||
async fn after_spawn(
|
||||
&mut self,
|
||||
entity_type: &'static EntityType,
|
||||
pos: &BlockPos,
|
||||
world: &Arc<World>,
|
||||
) {
|
||||
let charge;
|
||||
if self.last_checked_pos.eq(pos) && self.last_checked_type == entity_type {
|
||||
charge = self.last_charge;
|
||||
} else {
|
||||
// TODO get biome
|
||||
let biome = world.level.get_rough_biome(pos).await;
|
||||
if let Some(cost) = biome.spawn_costs.get(entity_type.resource_name) {
|
||||
charge = cost.charge;
|
||||
} else {
|
||||
charge = 0.;
|
||||
}
|
||||
}
|
||||
|
||||
self.spawn_potential.add_charge(pos, charge);
|
||||
self.mob_category_counts.add(entity_type.category);
|
||||
self.local_mob_cap_calculator
|
||||
.add_mob(
|
||||
&Vector2::<i32>::new(get_section_cord(pos.0.x), get_section_cord(pos.0.z)),
|
||||
entity_type.category,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_filtered_spawning_categories(
|
||||
state: &SpawnState,
|
||||
spawn_friendlies: bool,
|
||||
spawn_enemies: bool,
|
||||
spawn_passives: bool,
|
||||
) -> Vec<&'static MobCategory> {
|
||||
let mut ret = Vec::with_capacity(8);
|
||||
for category in MobCategory::SPAWNING_CATEGORIES {
|
||||
if (spawn_friendlies || !category.is_friendly)
|
||||
&& (spawn_enemies || category.is_friendly)
|
||||
&& (spawn_passives || !category.is_persistent)
|
||||
&& state.can_spawn_for_category_global(category)
|
||||
{
|
||||
ret.push(category);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn spawn_for_chunk(
|
||||
world: &Arc<World>,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
spawn_state: &mut SpawnState,
|
||||
spawn_list: &Vec<&'static MobCategory>,
|
||||
) {
|
||||
// debug!("spawn for chunk {:?}", chunk_pos);
|
||||
for category in spawn_list {
|
||||
if spawn_state
|
||||
.can_spawn_for_category_local(category, chunk_pos)
|
||||
.await
|
||||
{
|
||||
let random_pos = get_random_pos_within(world.min_y, chunk_pos, chunk).await;
|
||||
// debug!("try random pos: {:?}", random_pos);
|
||||
if random_pos.0.y > world.min_y {
|
||||
spawn_category_for_position(category, world, random_pos, chunk_pos, spawn_state)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn get_random_pos_within(
|
||||
min_y: i32,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
) -> BlockPos {
|
||||
let x = (chunk_pos.x << 4) + rng().random_range(0..16);
|
||||
let z = (chunk_pos.y << 4) + rng().random_range(0..16);
|
||||
let temp_y =
|
||||
chunk
|
||||
.read()
|
||||
.await
|
||||
.heightmap
|
||||
.get_height(ChunkHeightmapType::WorldSurface, x, z, min_y)
|
||||
+ 1;
|
||||
let y = rng().random_range(min_y..=temp_y);
|
||||
BlockPos::new(x, y, z)
|
||||
}
|
||||
|
||||
pub async fn spawn_category_for_position(
|
||||
category: &'static MobCategory,
|
||||
world: &Arc<World>,
|
||||
pos: BlockPos,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
spawn_state: &mut SpawnState,
|
||||
) {
|
||||
// TODO StructureManager structureManager = level.structureManager();
|
||||
// TODO blockState.isRedstoneConductor(chunk, pos) is true then return
|
||||
let mut spawn_cluster_size = 0;
|
||||
let mut new_pos = pos;
|
||||
for _ in 0..3 {
|
||||
let mut new_x = new_pos.0.x;
|
||||
let mut new_z = new_pos.0.z;
|
||||
let mut random_group_size = (rng().random::<f32>() * 4.).ceil() as i32;
|
||||
let mut inc = 0;
|
||||
#[allow(unused_variables)]
|
||||
let mut group_size = 0;
|
||||
'outer: while inc < random_group_size {
|
||||
new_x += rng().random_range(0..6) - rng().random_range(0..6);
|
||||
new_z += rng().random_range(0..6) - rng().random_range(0..6);
|
||||
new_pos = BlockPos::new(new_x, new_pos.0.y, new_z);
|
||||
let new_pos_center = new_pos.to_centered_f64();
|
||||
let player_distance = get_nearest_player(&new_pos_center, world).await;
|
||||
if player_distance == f64::MAX {
|
||||
// debug!("player_distance infinity");
|
||||
return;
|
||||
}
|
||||
if !is_right_distance_to_player_and_spawn_point(
|
||||
&new_pos,
|
||||
player_distance,
|
||||
world,
|
||||
chunk_pos,
|
||||
) {
|
||||
// debug!("{new_pos:?} failed, too near to player or spawn point dst: {player_distance}");
|
||||
inc += 1;
|
||||
continue;
|
||||
}
|
||||
let Some(spawner) = get_random_spawn_mob_at(world, category, &new_pos).await else {
|
||||
// debug!("{new_pos:?} failed, no random spawn mob at category: {category:?}");
|
||||
break 'outer;
|
||||
};
|
||||
random_group_size = rng().random_range(spawner.min_count..=spawner.max_count);
|
||||
let entity_type =
|
||||
&EntityType::from_name(spawner.r#type.strip_prefix("minecraft:").unwrap()).unwrap();
|
||||
if !is_valid_spawn_position_for_type(
|
||||
world,
|
||||
&new_pos,
|
||||
category,
|
||||
entity_type,
|
||||
player_distance,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// debug!("{new_pos:?} failed, not valid spawn position");
|
||||
inc += 1;
|
||||
continue;
|
||||
}
|
||||
if !spawn_state.can_spawn(entity_type, &new_pos, world).await {
|
||||
// debug!("{new_pos:?} failed, can't spawn at");
|
||||
inc += 1;
|
||||
continue;
|
||||
}
|
||||
let entity = from_type(entity_type, new_pos_center, world, Uuid::new_v4()).await;
|
||||
entity
|
||||
.get_entity()
|
||||
.set_rotation(rng().random::<f32>() * 360., 0.);
|
||||
// TODO isValidPositionForMob(level, mob, f)
|
||||
// TODO spawnGroupData = mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData);
|
||||
spawn_cluster_size += 1;
|
||||
group_size += 1;
|
||||
world.spawn_entity(entity).await;
|
||||
spawn_state.after_spawn(entity_type, &new_pos, world).await;
|
||||
if spawn_cluster_size >= entity_type.limit_per_chunk {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO mob.isMaxGroupSizeReached(p)
|
||||
inc += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_nearest_player(pos: &Vector3<f64>, world: &Arc<World>) -> f64 {
|
||||
let mut dst = f64::MAX;
|
||||
for (_uuid, player) in world.players.read().await.iter() {
|
||||
if player.gamemode.load() == GameMode::Spectator {
|
||||
continue;
|
||||
}
|
||||
let cur_dst = player.position().squared_distance_to_vec(*pos);
|
||||
if cur_dst < dst {
|
||||
dst = cur_dst;
|
||||
}
|
||||
}
|
||||
dst
|
||||
}
|
||||
pub fn is_right_distance_to_player_and_spawn_point(
|
||||
pos: &BlockPos,
|
||||
distance: f64,
|
||||
_world: &Arc<World>,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
) -> bool {
|
||||
if distance <= 24. * 24. {
|
||||
return false;
|
||||
}
|
||||
// TODO getSharedSpawnPos/WorldSpawnPoint
|
||||
if pos.to_centered_f64().squared_distance_to(0., 0., 0.) <= 24. * 24. {
|
||||
return false;
|
||||
}
|
||||
#[allow(clippy::overly_complex_bool_expr)]
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
{
|
||||
chunk_pos == &Vector2::new(get_section_cord(pos.0.x), get_section_cord(pos.0.z)) || false // TODO canSpawnEntitiesInChunk(ChunkPos chunkPos)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub async fn get_random_spawn_mob_at(
|
||||
world: &Arc<World>,
|
||||
category: &'static MobCategory,
|
||||
block_pos: &BlockPos,
|
||||
) -> Option<&'static Spawner> {
|
||||
// TODO Holder<Biome> holder = level.getBiome(pos);
|
||||
let biome = world.level.get_rough_biome(block_pos).await;
|
||||
if category == &MobCategory::WATER_AMBIENT
|
||||
&& biome.is_tagged_with_by_tag(&MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS)
|
||||
&& rng().random::<f32>() < 0.98f32
|
||||
{
|
||||
None
|
||||
} else {
|
||||
// TODO isInNetherFortressBounds(pos, level, cetagory, structureManager) then NetherFortressStructure.FORTRESS_ENEMIES
|
||||
// TODO structureManager.getAllStructuresAt(pos); ChunkGenerator::getMobsAt
|
||||
match category.id {
|
||||
id if id == MobCategory::MONSTER.id => biome.spawners.monster,
|
||||
id if id == MobCategory::CREATURE.id => biome.spawners.creature,
|
||||
id if id == MobCategory::AMBIENT.id => biome.spawners.ambient,
|
||||
id if id == MobCategory::AXOLOTLS.id => biome.spawners.axolotls,
|
||||
id if id == MobCategory::UNDERGROUND_WATER_CREATURE.id => {
|
||||
biome.spawners.underground_water_creature
|
||||
}
|
||||
id if id == MobCategory::WATER_CREATURE.id => biome.spawners.water_creature,
|
||||
id if id == MobCategory::WATER_AMBIENT.id => biome.spawners.water_ambient,
|
||||
id if id == MobCategory::MISC.id => biome.spawners.misc,
|
||||
_ => panic!(),
|
||||
}
|
||||
.choose(&mut rng())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_valid_spawn_position_for_type(
|
||||
world: &Arc<World>,
|
||||
block_pos: &BlockPos,
|
||||
category: &'static MobCategory,
|
||||
entity_type: &'static EntityType,
|
||||
distance: f64,
|
||||
) -> bool {
|
||||
// TODO !SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
|
||||
// TODO level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5))
|
||||
!(category == &MobCategory::MISC
|
||||
|| (!entity_type.can_spawn_far_from_player
|
||||
&& distance
|
||||
> f64::from(entity_type.category.despawn_distance)
|
||||
* f64::from(entity_type.category.despawn_distance))
|
||||
|| !entity_type.summonable
|
||||
|| !is_spawn_position_ok(world, block_pos, entity_type).await)
|
||||
}
|
||||
|
||||
pub async fn is_spawn_position_ok(
|
||||
world: &Arc<World>,
|
||||
block_pos: &BlockPos,
|
||||
entity_type: &'static EntityType,
|
||||
) -> bool {
|
||||
match entity_type.spawn_restriction.location {
|
||||
SpawnLocation::InLava => world
|
||||
.get_fluid(block_pos)
|
||||
.await
|
||||
.is_tagged_with_by_tag(&MINECRAFT_LAVA),
|
||||
SpawnLocation::InWater => {
|
||||
// TODO !level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)
|
||||
world
|
||||
.get_fluid(block_pos)
|
||||
.await
|
||||
.is_tagged_with_by_tag(&MINECRAFT_WATER)
|
||||
}
|
||||
SpawnLocation::OnGround => {
|
||||
let down = world.get_block_state(&block_pos.down()).await;
|
||||
let up = world.get_block_state(&block_pos.up()).await;
|
||||
let cur = world.get_block_state(block_pos).await;
|
||||
if down.is_side_solid(BlockDirection::Up) {
|
||||
is_valid_empty_spawn_block(cur) && is_valid_empty_spawn_block(up)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
SpawnLocation::Unrestricted => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid_empty_spawn_block(state: &'static BlockState) -> bool {
|
||||
if state.is_solid() {
|
||||
false
|
||||
} else if false {
|
||||
// TODO isSignalSource
|
||||
false
|
||||
} else if state.is_liquid() {
|
||||
false
|
||||
} else {
|
||||
// TODO !entityType.isBlockDangerous(blockState);
|
||||
!Block::from_state_id(state.id)
|
||||
.is_tagged_with_by_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user