mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: implement Mob Spawners
This commit is contained in:
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -125,10 +125,12 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.27"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
|
||||
checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
@@ -269,9 +271,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.2"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@@ -422,6 +424,25 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905"
|
||||
dependencies = [
|
||||
"compression-core",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compression-core"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01"
|
||||
|
||||
[[package]]
|
||||
name = "console-api"
|
||||
version = "0.8.1"
|
||||
@@ -496,7 +517,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"document-features",
|
||||
"idna",
|
||||
"indexmap 2.10.0",
|
||||
"indexmap 2.11.0",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -1075,7 +1096,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.10.0",
|
||||
"indexmap 2.11.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -1384,9 +1405,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
@@ -1825,9 +1846,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
@@ -1836,9 +1857,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared",
|
||||
@@ -1846,9 +1867,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d713258393a82f091ead52047ca779d37e5766226d009de21696c4e667044368"
|
||||
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
@@ -1859,9 +1880,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.12.1"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
@@ -2168,7 +2189,7 @@ dependencies = [
|
||||
name = "pumpkin-registry"
|
||||
version = "0.1.0-dev+1.21.8"
|
||||
dependencies = [
|
||||
"indexmap 2.10.0",
|
||||
"indexmap 2.11.0",
|
||||
"pumpkin-protocol",
|
||||
"pumpkin-util",
|
||||
"serde",
|
||||
@@ -2734,9 +2755,12 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@@ -3013,7 +3037,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8"
|
||||
dependencies = [
|
||||
"indexmap 2.10.0",
|
||||
"indexmap 2.11.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
||||
build = "build/build.rs"
|
||||
|
||||
[dependencies]
|
||||
phf = { version = "0.12.1", features = ["macros"] }
|
||||
phf = { version = "0.13.1", features = ["macros"] }
|
||||
pumpkin-util = { path = "../pumpkin-util" }
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
|
||||
|
||||
@@ -69,16 +69,16 @@ pub(crate) fn build() -> TokenStream {
|
||||
let mut name_to_type = TokenStream::new();
|
||||
let mut minecraft_name_to_type = TokenStream::new();
|
||||
|
||||
for (name, effect) in effects.iter() {
|
||||
for (name, effect) in effects.into_iter() {
|
||||
let format_name = format_ident!("{}", name.to_shouty_snake_case());
|
||||
let id = effect.id;
|
||||
let color = effect.color;
|
||||
let translation_key = effect.translation_key.clone();
|
||||
let translation_key = effect.translation_key;
|
||||
let category = effect.category.to_tokens();
|
||||
let slots = effect.attribute_modifiers.clone();
|
||||
let slots = effect.attribute_modifiers;
|
||||
let slots = slots.iter().map(|slot| slot.to_tokens());
|
||||
|
||||
let minecraft_name = "minecraft:".to_string() + name;
|
||||
let minecraft_name = "minecraft:".to_string() + &name;
|
||||
variants.extend([quote! {
|
||||
pub const #format_name: Self = Self {
|
||||
minecraft_name: #minecraft_name,
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
let mut name_to_type = TokenStream::new();
|
||||
let mut id_to_type = TokenStream::new();
|
||||
|
||||
for (name, enchantment) in enchantments.iter() {
|
||||
for (name, enchantment) in enchantments.into_iter() {
|
||||
let id = enchantment.id;
|
||||
let raw_name = name.strip_prefix("minecraft:").unwrap();
|
||||
let format_name = format_ident!("{}", raw_name.to_shouty_snake_case());
|
||||
@@ -79,7 +79,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
.to_uppercase()
|
||||
);
|
||||
let max_level = enchantment.max_level;
|
||||
let slots = enchantment.slots.clone();
|
||||
let slots = enchantment.slots;
|
||||
let slots = slots.iter().map(|slot| slot.to_tokens());
|
||||
let Translate { translate, with: _ } = &enchantment.description.0.content else {
|
||||
panic!()
|
||||
|
||||
@@ -53,8 +53,8 @@ impl ToTokens for PropertyStruct {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = Ident::new(&self.name, Span::call_site());
|
||||
|
||||
let variant_count = self.values.clone().len() as u16;
|
||||
let values_index = (0..self.values.clone().len() as u16).collect::<Vec<_>>();
|
||||
let variant_count = self.values.len() as u16;
|
||||
let values_index = (0..self.values.len() as u16).collect::<Vec<_>>();
|
||||
|
||||
let ident_values = self.values.iter().map(|value| {
|
||||
let value_str = if value.chars().all(|c| c.is_numeric()) {
|
||||
@@ -370,7 +370,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
let mut unique_states = Vec::new();
|
||||
let mut optimized_fluids: Vec<(String, FluidStateRef)> = Vec::new();
|
||||
|
||||
for fluid in fluids.clone() {
|
||||
for fluid in fluids {
|
||||
let id_name = LitStr::new(&fluid.name, proc_macro2::Span::call_site());
|
||||
let const_ident = format_ident!("{}", fluid.name.to_shouty_snake_case());
|
||||
let state_id_start = fluid
|
||||
|
||||
@@ -59,10 +59,10 @@ pub(crate) fn build() -> TokenStream {
|
||||
let mut variants = TokenStream::new();
|
||||
let mut name_to_type = TokenStream::new();
|
||||
|
||||
for (name, potion) in potions.iter() {
|
||||
for (name, potion) in potions.into_iter() {
|
||||
let format_name = format_ident!("{}", name.to_shouty_snake_case());
|
||||
let id = potion.id;
|
||||
let slots = potion.effects.clone();
|
||||
let slots = potion.effects;
|
||||
let slots = slots.iter().map(|slot| slot.to_tokens());
|
||||
|
||||
variants.extend([quote! {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct Recipes {
|
||||
}
|
||||
|
||||
impl Recipes {
|
||||
pub fn to_tokens_potion(&self) -> TokenStream {
|
||||
pub fn get_tokens_potion(self) -> TokenStream {
|
||||
let from = format_ident!(
|
||||
"{}",
|
||||
self.from.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
@@ -28,8 +28,7 @@ impl Recipes {
|
||||
self.to.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
);
|
||||
|
||||
let slots = self.ingredient.clone();
|
||||
let slots = slots.iter().map(|slot| {
|
||||
let slots = self.ingredient.iter().map(|slot| {
|
||||
format_ident!(
|
||||
"{}",
|
||||
slot.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
@@ -45,7 +44,7 @@ impl Recipes {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_tokens_item(&self) -> TokenStream {
|
||||
pub fn get_tokens_item(self) -> TokenStream {
|
||||
let from = format_ident!(
|
||||
"{}",
|
||||
self.from.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
@@ -55,8 +54,7 @@ impl Recipes {
|
||||
self.to.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
);
|
||||
|
||||
let slots = self.ingredient.clone();
|
||||
let slots = slots.iter().map(|slot| {
|
||||
let slots = self.ingredient.iter().map(|slot| {
|
||||
format_ident!(
|
||||
"{}",
|
||||
slot.strip_prefix("minecraft:").unwrap().to_uppercase()
|
||||
@@ -83,16 +81,16 @@ pub(crate) fn build() -> TokenStream {
|
||||
let mut item = TokenStream::new();
|
||||
let mut potion = TokenStream::new();
|
||||
|
||||
for j in &json.item_recipes {
|
||||
item.extend(j.to_tokens_item());
|
||||
}
|
||||
for j in &json.potion_recipes {
|
||||
potion.extend(j.to_tokens_potion());
|
||||
}
|
||||
|
||||
let item_len = json.item_recipes.len();
|
||||
let potion_len = json.potion_recipes.len();
|
||||
|
||||
for j in json.item_recipes {
|
||||
item.extend(j.get_tokens_item());
|
||||
}
|
||||
for j in json.potion_recipes {
|
||||
potion.extend(j.get_tokens_potion());
|
||||
}
|
||||
|
||||
quote! {
|
||||
#![allow(dead_code)]
|
||||
use crate::potion::Potion;
|
||||
|
||||
@@ -11,10 +11,12 @@ pub(crate) fn build() -> TokenStream {
|
||||
serde_json::from_str(&fs::read_to_string("../assets/spawn_egg.json").unwrap())
|
||||
.expect("Failed to parse spawn_egg.json");
|
||||
let mut names = TokenStream::new();
|
||||
let mut ids = TokenStream::new();
|
||||
|
||||
for (egg, entity) in &eggs {
|
||||
let entity = entity.to_shouty_snake_case();
|
||||
let entity = format_ident!("{}", entity);
|
||||
ids.extend(quote! { #egg, });
|
||||
names.extend(quote! { #egg => Some(&EntityType::#entity), });
|
||||
}
|
||||
quote! {
|
||||
@@ -26,5 +28,8 @@ pub(crate) fn build() -> TokenStream {
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn spawn_egg_ids() -> Box<[u16]> {
|
||||
[#ids].into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ aes = "0.8"
|
||||
cfb8 = "0.8"
|
||||
|
||||
# compression
|
||||
async-compression = { version = "0.4.27", features = ["tokio", "zlib"] }
|
||||
async-compression = { version = "0.4.28", features = ["tokio", "zlib"] }
|
||||
|
||||
take_mut = "0.2.2"
|
||||
bitflags = "2.9.2"
|
||||
bitflags = "2.9.3"
|
||||
|
||||
[dev-dependencies]
|
||||
# Validate correctness
|
||||
|
||||
@@ -118,7 +118,6 @@ impl ClientPacket for CChunkData<'_> {
|
||||
write.write_slice(&blocks_and_biomes_buf)?;
|
||||
}
|
||||
|
||||
// TODO: block entities
|
||||
write.write_var_int(&VarInt(self.0.block_entities.len() as i32))?;
|
||||
for block_entity in self.0.block_entities.values() {
|
||||
let block_entity = &block_entity;
|
||||
|
||||
@@ -7,7 +7,7 @@ edition.workspace = true
|
||||
pumpkin-protocol = { path = "../pumpkin-protocol" }
|
||||
pumpkin-util = { path = "../pumpkin-util" }
|
||||
|
||||
indexmap = { version = "2.10", features = ["serde"] }
|
||||
indexmap = { version = "2.11", features = ["serde"] }
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -26,7 +26,7 @@ crossbeam.workspace = true
|
||||
|
||||
num-derive = "0.4"
|
||||
sha2 = "0.10"
|
||||
bitflags = "2.9.2"
|
||||
bitflags = "2.9.3"
|
||||
|
||||
dashmap = "6.1"
|
||||
|
||||
|
||||
186
pumpkin-world/src/block/entities/mob_spawner.rs
Normal file
186
pumpkin-world/src/block/entities/mob_spawner.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicI32, Ordering},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use pumpkin_data::{entity::EntityType, world::WorldEvent};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::{
|
||||
boundingbox::{BoundingBox, EntityDimensions},
|
||||
position::BlockPos,
|
||||
vector3::Vector3,
|
||||
};
|
||||
|
||||
use crate::{block::entities::BlockEntity, world::SimpleWorld};
|
||||
|
||||
pub struct MobSpawnerBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub delay: AtomicI32,
|
||||
pub max_delay: i32,
|
||||
pub min_delay: i32,
|
||||
pub spawn_count: i32,
|
||||
pub spawn_range: i32,
|
||||
pub entity_type: AtomicCell<Option<&'static EntityType>>,
|
||||
}
|
||||
|
||||
impl MobSpawnerBlockEntity {
|
||||
pub const ID: &'static str = "minecraft:mob_spawner";
|
||||
pub const DEFAULT_DELAY: i32 = 20;
|
||||
pub const DEFAULT_MAX_SPAWN_DELAY: i32 = 800;
|
||||
pub const DEFAULT_MIN_SPAWN_DELAY: i32 = 200;
|
||||
pub const DEFAULT_SPAWN_COUNT: i32 = 4;
|
||||
pub const DEFAULT_SPAWN_RANGE: i32 = 4;
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
delay: AtomicI32::new(Self::DEFAULT_DELAY),
|
||||
max_delay: Self::DEFAULT_MAX_SPAWN_DELAY,
|
||||
min_delay: Self::DEFAULT_MIN_SPAWN_DELAY,
|
||||
spawn_count: Self::DEFAULT_SPAWN_COUNT,
|
||||
spawn_range: Self::DEFAULT_SPAWN_RANGE,
|
||||
entity_type: AtomicCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MobSpawnerBlockEntity {
|
||||
async fn update_spawns(&self, world: &Arc<dyn SimpleWorld>) {
|
||||
let min_delay = self.min_delay;
|
||||
let max_delay = self.max_delay;
|
||||
|
||||
self.delay.store(
|
||||
if max_delay <= min_delay {
|
||||
min_delay
|
||||
} else {
|
||||
min_delay + rand::random_range(0..max_delay - min_delay)
|
||||
},
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
world.add_synced_block_event(self.position, 1, 0).await
|
||||
}
|
||||
|
||||
pub fn set_entity_type(&self, entity_type: &'static EntityType) {
|
||||
self.entity_type.store(Some(entity_type));
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockEntity for MobSpawnerBlockEntity {
|
||||
fn resource_location(&self) -> &'static str {
|
||||
Self::ID
|
||||
}
|
||||
|
||||
fn get_position(&self) -> BlockPos {
|
||||
self.position
|
||||
}
|
||||
|
||||
async fn tick(&self, world: Arc<dyn SimpleWorld>) {
|
||||
if let Some(entity_type) = &self.entity_type.load() {
|
||||
if self.delay.load(Ordering::Relaxed) == -1 {
|
||||
self.update_spawns(&world).await;
|
||||
} else {
|
||||
self.delay.fetch_sub(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
let spawn_range = self.spawn_range;
|
||||
let mut update_spawns = false;
|
||||
for _ in 0..self.spawn_count {
|
||||
let pos = self.position.0;
|
||||
|
||||
let spawn_pos = Vector3::new(
|
||||
pos.x as f64
|
||||
+ (rand::random::<f64>() + rand::random::<f64>()) * spawn_range as f64
|
||||
+ 0.5,
|
||||
(pos.y + rand::random_range(0..3) - 1) as f64,
|
||||
pos.z as f64
|
||||
+ (rand::random::<f64>() + rand::random::<f64>()) * spawn_range as f64
|
||||
+ 0.5,
|
||||
);
|
||||
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
|
||||
if !world
|
||||
.is_space_empty(BoundingBox::new_from_pos(
|
||||
spawn_pos.x,
|
||||
spawn_pos.y,
|
||||
spawn_pos.z,
|
||||
&EntityDimensions {
|
||||
width: entity_type.dimension[0],
|
||||
height: entity_type.dimension[1],
|
||||
},
|
||||
))
|
||||
.await
|
||||
{
|
||||
continue;
|
||||
}
|
||||
world.clone().spawn_from_type(entity_type, spawn_pos).await;
|
||||
world
|
||||
.sync_world_event(WorldEvent::SpawnerSpawnsMob, self.position, 0)
|
||||
.await;
|
||||
update_spawns = true;
|
||||
}
|
||||
if update_spawns {
|
||||
self.update_spawns(&world).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let delay = nbt.get_short("Delay").unwrap_or(Self::DEFAULT_DELAY as i16) as i32;
|
||||
let min_delay = nbt
|
||||
.get_int("MinSpawnDelay")
|
||||
.unwrap_or(Self::DEFAULT_MIN_SPAWN_DELAY);
|
||||
let max_delay = nbt
|
||||
.get_int("MaxSpawnDelay")
|
||||
.unwrap_or(Self::DEFAULT_MAX_SPAWN_DELAY);
|
||||
let spawn_count = nbt
|
||||
.get_int("SpawnCount")
|
||||
.unwrap_or(Self::DEFAULT_SPAWN_COUNT);
|
||||
let spawn_range = nbt
|
||||
.get_int("SpawnRange")
|
||||
.unwrap_or(Self::DEFAULT_SPAWN_RANGE);
|
||||
let _spawn_entry = nbt.get_compound("SpawnData");
|
||||
|
||||
Self {
|
||||
position,
|
||||
delay: AtomicI32::new(delay),
|
||||
max_delay,
|
||||
min_delay,
|
||||
spawn_count,
|
||||
spawn_range,
|
||||
entity_type: AtomicCell::new(None), // TODO
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
|
||||
if let Some(entity_type) = self.entity_type.load() {
|
||||
let mut entity_nbt = NbtCompound::new();
|
||||
entity_nbt.put_string("id", format!("minecraft:{}", entity_type.resource_name));
|
||||
|
||||
nbt.put_component("entity", entity_nbt);
|
||||
}
|
||||
}
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut final_nbt = NbtCompound::new();
|
||||
if let Some(entity_type) = self.entity_type.load() {
|
||||
let mut spawn_entry = NbtCompound::new();
|
||||
|
||||
let mut entity_nbt = NbtCompound::new();
|
||||
entity_nbt.put_string("id", format!("minecraft:{}", entity_type.resource_name));
|
||||
|
||||
spawn_entry.put_component("entity", entity_nbt);
|
||||
|
||||
final_nbt.put_component("SpawnData", spawn_entry);
|
||||
}
|
||||
Some(final_nbt)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use pumpkin_util::math::position::BlockPos;
|
||||
use sign::SignBlockEntity;
|
||||
|
||||
use crate::block::entities::hopper::HopperBlockEntity;
|
||||
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
||||
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
|
||||
use crate::{
|
||||
BlockStateId, block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity,
|
||||
@@ -30,6 +31,7 @@ pub mod dropper;
|
||||
pub mod end_portal;
|
||||
pub mod furnace;
|
||||
pub mod hopper;
|
||||
pub mod mob_spawner;
|
||||
pub mod piston;
|
||||
pub mod shulker_box;
|
||||
pub mod sign;
|
||||
@@ -75,6 +77,7 @@ pub trait BlockEntity: Send + Sync {
|
||||
fn is_dirty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
|
||||
None
|
||||
@@ -98,6 +101,9 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
|
||||
}
|
||||
BarrelBlockEntity::ID => Arc::new(block_entity_from_generic::<BarrelBlockEntity>(nbt)),
|
||||
HopperBlockEntity::ID => Arc::new(block_entity_from_generic::<HopperBlockEntity>(nbt)),
|
||||
MobSpawnerBlockEntity::ID => {
|
||||
Arc::new(block_entity_from_generic::<MobSpawnerBlockEntity>(nbt))
|
||||
}
|
||||
DropperBlockEntity::ID => Arc::new(block_entity_from_generic::<DropperBlockEntity>(nbt)),
|
||||
ShulkerBoxBlockEntity::ID => {
|
||||
Arc::new(block_entity_from_generic::<ShulkerBoxBlockEntity>(nbt))
|
||||
|
||||
@@ -134,7 +134,7 @@ impl BlockEntity for PistonBlockEntity {
|
||||
nbt.put_float(LAST_PROGRESS, self.last_progress.load());
|
||||
nbt.put_bool(EXTENDING, self.extending);
|
||||
nbt.put_bool(SOURCE, self.source);
|
||||
// TODO: duplicated code :c
|
||||
// TODO: duplicated code because of async :c
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ use crate::block::entities::BlockEntity;
|
||||
use crate::{BlockStateId, inventory::Inventory};
|
||||
use async_trait::async_trait;
|
||||
use bitflags::bitflags;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use thiserror::Error;
|
||||
@@ -58,8 +61,18 @@ pub trait SimpleWorld: BlockAccessor + Send + Sync {
|
||||
except: Option<BlockDirection>,
|
||||
);
|
||||
|
||||
async fn is_space_empty(&self, bounding_box: BoundingBox) -> bool;
|
||||
|
||||
async fn spawn_from_type(
|
||||
self: Arc<Self>,
|
||||
entity_type: &'static EntityType,
|
||||
position: Vector3<f64>,
|
||||
);
|
||||
|
||||
async fn add_synced_block_event(&self, pos: BlockPos, r#type: u8, data: u8);
|
||||
|
||||
async fn sync_world_event(&self, world_event: WorldEvent, position: BlockPos, data: i32);
|
||||
|
||||
async fn remove_block_entity(&self, block_pos: &BlockPos);
|
||||
async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>>;
|
||||
async fn get_world_age(&self) -> i64;
|
||||
@@ -82,7 +95,6 @@ pub trait SimpleWorld: BlockAccessor + Send + Sync {
|
||||
);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait BlockRegistryExt: Send + Sync {
|
||||
fn can_place_at(
|
||||
&self,
|
||||
|
||||
@@ -44,6 +44,7 @@ pub mod redstone;
|
||||
pub mod signs;
|
||||
pub mod skull_block;
|
||||
pub mod slabs;
|
||||
pub mod spawner;
|
||||
pub mod stairs;
|
||||
pub mod sugar_cane;
|
||||
pub mod tnt;
|
||||
|
||||
20
pumpkin/src/block/blocks/spawner.rs
Normal file
20
pumpkin/src/block/blocks/spawner.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
||||
|
||||
use crate::block::{BlockBehaviour, PlacedArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:spawner")]
|
||||
pub struct SpawnerBlock;
|
||||
|
||||
#[async_trait]
|
||||
impl BlockBehaviour for SpawnerBlock {
|
||||
async fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let hopper_block_entity = MobSpawnerBlockEntity::new(*args.position);
|
||||
args.world
|
||||
.add_block_entity(Arc::new(hopper_block_entity))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ use crate::block::blocks::redstone::tripwire::TripwireBlock;
|
||||
use crate::block::blocks::redstone::tripwire_hook::TripwireHookBlock;
|
||||
use crate::block::blocks::signs::SignBlock;
|
||||
use crate::block::blocks::slabs::SlabBlock;
|
||||
use crate::block::blocks::spawner::SpawnerBlock;
|
||||
use crate::block::blocks::stairs::StairBlock;
|
||||
use crate::block::blocks::sugar_cane::SugarCaneBlock;
|
||||
use crate::block::blocks::tnt::TNTBlock;
|
||||
@@ -74,7 +75,6 @@ use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::fluid;
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_data::item::Item;
|
||||
@@ -201,6 +201,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
manager.register(PressurePlateBlock);
|
||||
manager.register(WeightedPressurePlateBlock);
|
||||
manager.register(EndPortalBlock);
|
||||
manager.register(SpawnerBlock);
|
||||
manager.register(EndPortalFrameBlock);
|
||||
manager.register(CandleBlock);
|
||||
manager.register(SeaPickleBlock);
|
||||
@@ -290,7 +291,6 @@ pub struct BlockRegistry {
|
||||
fluids: HashMap<&'static Fluid, Arc<dyn FluidBehaviour>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockRegistryExt for BlockRegistry {
|
||||
fn can_place_at(
|
||||
&self,
|
||||
|
||||
@@ -14,12 +14,14 @@ pub mod minecart;
|
||||
pub mod name_tag;
|
||||
pub mod shovel;
|
||||
pub mod snowball;
|
||||
pub mod spawn_egg;
|
||||
pub mod swords;
|
||||
pub mod trident;
|
||||
|
||||
use crate::item::items::end_crystal::EndCrystalItem;
|
||||
use crate::item::items::minecart::MinecartItem;
|
||||
use crate::item::items::name_tag::NameTagItem;
|
||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||
|
||||
use super::registry::ItemRegistry;
|
||||
use axe::AxeItem;
|
||||
@@ -54,6 +56,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(EmptyBucketItem);
|
||||
manager.register(FilledBucketItem);
|
||||
manager.register(ShovelItem);
|
||||
manager.register(SpawnEggItem);
|
||||
manager.register(AxeItem);
|
||||
manager.register(EndCrystalItem);
|
||||
manager.register(MinecartItem);
|
||||
|
||||
71
pumpkin/src/item/items/spawn_egg.rs
Normal file
71
pumpkin/src/item/items/spawn_egg.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::r#type::from_type;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::entity::entity_from_egg;
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::math::wrap_degrees;
|
||||
use pumpkin_world::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct SpawnEggItem;
|
||||
|
||||
impl ItemMetadata for SpawnEggItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
pumpkin_data::entity::spawn_egg_ids()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ItemBehaviour for SpawnEggItem {
|
||||
async fn use_on_block(
|
||||
&self,
|
||||
item: &mut ItemStack,
|
||||
player: &Player,
|
||||
location: BlockPos,
|
||||
face: BlockDirection,
|
||||
_block: &Block,
|
||||
_server: &Server,
|
||||
) {
|
||||
if let Some(entity_type) = entity_from_egg(item.item.id) {
|
||||
let world = player.world();
|
||||
|
||||
if let Some(block_entity) = player.world().get_block_entity(&location).await
|
||||
&& let Some(spawner) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<MobSpawnerBlockEntity>()
|
||||
{
|
||||
spawner.set_entity_type(entity_type);
|
||||
world.update_block_entity(&block_entity).await;
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
return;
|
||||
}
|
||||
let pos = BlockPos(location.0 + face.to_offset());
|
||||
let pos = Vector3::new(
|
||||
f64::from(pos.0.x) + 0.5,
|
||||
f64::from(pos.0.y),
|
||||
f64::from(pos.0.z) + 0.5,
|
||||
);
|
||||
// Create rotation like Vanilla
|
||||
let yaw = wrap_degrees(rand::random::<f32>() * 360.0) % 360.0;
|
||||
|
||||
let mob = from_type(entity_type, pos, world, Uuid::new_v4()).await;
|
||||
|
||||
// Set the rotation
|
||||
mob.get_entity().set_rotation(yaw, 0.0);
|
||||
|
||||
// Broadcast the new mob to all players
|
||||
world.spawn_entity(mob).await;
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
// TODO: send/configure additional commands/data based on the type of entity (horse, slime, etc)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ use crate::block::{self, BlockIsReplacing};
|
||||
use crate::command::CommandSender;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::{ChatMode, ChatSession, Player};
|
||||
use crate::entity::r#type::from_type;
|
||||
use crate::error::PumpkinError;
|
||||
use crate::net::PlayerConfig;
|
||||
use crate::net::java::JavaClient;
|
||||
@@ -28,7 +27,6 @@ use crate::world::{World, chunker};
|
||||
use pumpkin_config::{BASIC_CONFIG, advanced_config};
|
||||
use pumpkin_data::block_properties::{BlockProperties, WaterLikeProperties};
|
||||
use pumpkin_data::data_component_impl::{ConsumableImpl, EquipmentSlot, FoodImpl};
|
||||
use pumpkin_data::entity::{EntityType, entity_from_egg};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||
@@ -60,7 +58,6 @@ use pumpkin_world::block::entities::sign::SignBlockEntity;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// In secure chat mode, Player will be kicked if they send a chat message with a timestamp that is older than this (in ms)
|
||||
/// Vanilla: 2 minutes
|
||||
@@ -1460,14 +1457,6 @@ impl JavaClient {
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Check if the item is a spawn egg
|
||||
let item_id = stack.item.id;
|
||||
if let Some(entity) = entity_from_egg(item_id) {
|
||||
self.spawn_entity_from_egg(player, entity, position, face)
|
||||
.await;
|
||||
should_try_decrement = true;
|
||||
}
|
||||
|
||||
if should_try_decrement {
|
||||
// TODO: Config
|
||||
// Decrease block count
|
||||
@@ -1751,36 +1740,6 @@ impl JavaClient {
|
||||
);
|
||||
}
|
||||
|
||||
async fn spawn_entity_from_egg(
|
||||
&self,
|
||||
player: &Player,
|
||||
entity_type: &'static EntityType,
|
||||
location: BlockPos,
|
||||
face: BlockDirection,
|
||||
) {
|
||||
let world_pos = BlockPos(location.0 + face.to_offset());
|
||||
// Align the position like Vanilla does
|
||||
let pos = Vector3::new(
|
||||
f64::from(world_pos.0.x) + 0.5,
|
||||
f64::from(world_pos.0.y),
|
||||
f64::from(world_pos.0.z) + 0.5,
|
||||
);
|
||||
// Create rotation like Vanilla
|
||||
let yaw = wrap_degrees(rand::random::<f32>() * 360.0) % 360.0;
|
||||
|
||||
let world = player.world();
|
||||
// Create a new mob and UUID based on the spawn egg id
|
||||
let mob = from_type(entity_type, pos, world, Uuid::new_v4()).await;
|
||||
|
||||
// Set the rotation
|
||||
mob.get_entity().set_rotation(yaw, 0.0);
|
||||
|
||||
// Broadcast the new mob to all players
|
||||
world.spawn_entity(mob).await;
|
||||
|
||||
// TODO: send/configure additional commands/data based on the type of entity (horse, slime, etc)
|
||||
}
|
||||
|
||||
const WORLD_LOWEST_Y: i8 = -64;
|
||||
const WORLD_MAX_Y: u16 = 320;
|
||||
|
||||
|
||||
@@ -3202,10 +3202,27 @@ impl pumpkin_world::world::SimpleWorld for World {
|
||||
Self::update_neighbors(&self, block_pos, except).await;
|
||||
}
|
||||
|
||||
async fn is_space_empty(&self, bounding_box: BoundingBox) -> bool {
|
||||
self.is_space_empty(bounding_box).await
|
||||
}
|
||||
|
||||
async fn add_synced_block_event(&self, pos: BlockPos, r#type: u8, data: u8) {
|
||||
self.add_synced_block_event(pos, r#type, data).await;
|
||||
}
|
||||
|
||||
async fn sync_world_event(&self, world_event: WorldEvent, position: BlockPos, data: i32) {
|
||||
self.sync_world_event(world_event, position, data).await;
|
||||
}
|
||||
|
||||
async fn spawn_from_type(
|
||||
self: Arc<Self>,
|
||||
entity_type: &'static EntityType,
|
||||
position: Vector3<f64>,
|
||||
) {
|
||||
let mob = from_type(entity_type, position, &self, Uuid::new_v4()).await;
|
||||
self.spawn_entity(mob).await;
|
||||
}
|
||||
|
||||
async fn remove_block_entity(&self, block_pos: &BlockPos) {
|
||||
self.remove_block_entity(block_pos).await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user