feat: more plugin entity stuff

This commit is contained in:
Alexander Medvedev
2026-08-27 12:36:26 +02:00
parent 3d69190a37
commit 800f7d70e5
68 changed files with 4366 additions and 1700 deletions

21
Cargo.lock generated
View File

@@ -1179,19 +1179,6 @@ version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crossfire"
version = "3.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111ce8f7abfbac38b4bc4f32a3a2dda1a8034b873c90c7899f302cc9dbbc05ec"
dependencies = [
"crossbeam-utils",
"futures-core",
"parking_lot",
"pointers",
"smallvec",
]
[[package]]
name = "crunchy"
version = "0.2.4"
@@ -3098,12 +3085,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "pointers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcdc93847ad24990939cce6e1804361e903efcb5f99daa5abd87943a9d6d7ba"
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -3464,7 +3445,6 @@ dependencies = [
"pumpkin-world",
"rand",
"thiserror 2.0.20",
"tokio",
"tracing",
]
@@ -3581,7 +3561,6 @@ dependencies = [
"bytes",
"criterion",
"crossbeam",
"crossfire",
"dashmap",
"flate2",
"futures",

View File

@@ -131,7 +131,7 @@ thiserror = { version = "2.0", default-features = false }
bytes = { version = "1.12", default-features = false, features = ["std"] }
# Concurrency/Parallelism and Synchronization
futures = { version = "0.3", default-features = false, features = ["executor"] }
futures = { version = "0.3", default-features = false, features = ["std"] }
rayon = { version = "1.12", default-features = false }
crossbeam = { version = "0.8", default-features = false, features = ["std"] }
@@ -153,7 +153,6 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = { version = "1.10.0", default-features = false, features = ["std"] }
criterion = { version = "0.8", default-features = false }
crossbeam-utils = { version = "0.8.22", default-features = false, features = ["std"] }
crossfire = { version = "3.1.19", default-features = false, features = ["compat"] }
crypto-bigint = { version = "0.7.5", default-features = false }
dashmap = { version = "6.2", default-features = false }
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }

View File

@@ -24,7 +24,6 @@ pumpkin-util.workspace = true
rand.workspace = true
tracing.workspace = true
tokio.workspace = true
thiserror.workspace = true
[lints]

View File

@@ -0,0 +1,31 @@
//! Plugin datapack management and querying utilities.
//!
//! This module provides interfaces to inspect, query, enable, disable, and reload
//! datapacks on the server.
//!
//! # Examples
//!
//! ## Listing Datapacks
//! ```rust,ignore
//! use pumpkin_plugin_api::Server;
//!
//! fn log_datapacks(server: &Server) {
//! let manager = server.get_datapack_manager();
//! for pack in manager.list_all_packs() {
//! println!("Datapack {}: enabled = {}", pack.name, pack.is_enabled);
//! }
//! }
//! ```
//!
//! ## Enabling and Reloading Datapacks
//! ```rust,ignore
//! use pumpkin_plugin_api::{Server, datapack::EnablePosition};
//!
//! fn enable_custom_pack(server: &Server) -> Result<(), String> {
//! let manager = server.get_datapack_manager();
//! manager.enable_pack("my_custom_pack", EnablePosition::Last)?;
//! Ok(())
//! }
//! ```
pub use crate::wit::pumpkin::plugin::datapack::{DatapackInfo, DatapackManager, EnablePosition};

View File

@@ -0,0 +1,32 @@
//! Plugin inventory and container management utilities.
//!
//! This module provides a unified API for inspecting, modifying, and interacting
//! with player inventories, ender chests, custom GUIs, and container block entities.
//!
//! # Examples
//!
//! ## Inspecting and Modifying a Player's Inventory
//! ```rust,ignore
//! use pumpkin_plugin_api::{Player, ItemStack};
//!
//! fn equip_player(player: &Player) {
//! let inv = player.get_inventory();
//! inv.set_helmet(Some(ItemStack::new("minecraft:diamond_helmet", 1)));
//! inv.set_boots(Some(ItemStack::new("minecraft:diamond_boots", 1)));
//!
//! let storage = inv.as_inventory();
//! storage.set_item(0, Some(ItemStack::new("minecraft:diamond_sword", 1)));
//! }
//! ```
//!
//! ## Interacting with Custom GUIs
//! ```rust,ignore
//! use pumpkin_plugin_api::gui::Gui;
//!
//! fn setup_gui(gui: &Gui) {
//! let inv = gui.get_inventory();
//! inv.clear();
//! }
//! ```
pub use crate::wit::pumpkin::plugin::inventory::{Inventory, PlayerInventory};

View File

@@ -76,6 +76,8 @@ use crate::{
/// Plugin command registration and handling utilities.
pub mod commands;
/// Datapack management and query utilities.
pub mod datapack;
/// Display and interaction entity utilities and builders.
pub mod display;
/// Custom enchantment registration and builder utilities.
@@ -85,6 +87,10 @@ pub mod events;
mod ext;
/// Bedrock UI form builders.
pub mod forms;
/// Unified inventory and container management utilities.
pub mod inventory;
/// Specialized mob entity wrappers and helpers.
pub mod mobs;
/// Constants for plugin permissions.
///
/// Use these in your `PluginMetadata` to request access to specific host features.
@@ -110,18 +116,19 @@ pub use wit::pumpkin::plugin::{
advancement as advancement_wit, bedrock_packets, block_entity, boss_bar,
command as command_wit, common,
context::{self, Context, MarketplaceMetadata, Server},
damage_types as damage_types_wit, data_components, display as display_wit,
enchantments as enchantments_wit, entity,
damage_types as damage_types_wit, data_components, datapack as datapack_wit,
display as display_wit, enchantments as enchantments_wit, entity,
entity_types::EntityType,
event::{self as events_wit, EventType},
gui, i18n, ipc, item_stack, java_dialogs, java_packets, particles, permission, player,
recipe as recipe_wit, scoreboard, screens as screens_wit, server, statistics as statistics_wit,
text, uuid, world,
gui, i18n, inventory as inventory_wit, ipc, item_stack, java_dialogs, java_packets, particles,
permission, player, recipe as recipe_wit, scoreboard, screens as screens_wit, server,
statistics as statistics_wit, text, uuid, world,
};
// Convenience re-exports of commonly-used plugin types so plugin authors can
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
pub use damage_types_wit::DamageType;
pub use datapack::{DatapackInfo, DatapackManager, EnablePosition};
pub use display::{
BillboardMode, BlockDisplayEntity, DisplayEntity, DisplayEntityExt, DisplayTransformation,
EntityDisplayExt, InteractionEntity, ItemDisplayEntity, ItemDisplayEntityExt, ItemDisplayMode,
@@ -134,6 +141,13 @@ pub use enchantment::{
};
pub use events::{EventHandler, FromIntoEvent};
pub use ext::player::PlayerEnderChestExt;
pub use inventory::{Inventory, PlayerInventory};
pub use mobs::{
Ageable, AgeableData, Cat, CatData, Creeper, CreeperData, DyeColor, Enderman, EndermanData,
EntityCastExt, Fox, FoxData, IronGolem, IronGolemData, MobCast, MobData, Sheep, SheepData,
Shulker, ShulkerData, Slime, SlimeData, Villager, VillagerData, VillagerProfession, Wolf,
WolfData, Zombie, ZombieData,
};
pub use recipe::{
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder,
@@ -146,8 +160,8 @@ pub use wit::pumpkin::plugin::player::Player;
pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
pub use wit::pumpkin::plugin::server::Dimension;
pub use wit::pumpkin::plugin::world::{
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, RayTraceBlockResult,
RayTraceEntityResult, RaycastResult, World, WorldBorder,
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, LivingEntity, Mob,
PathNodeType, RayTraceBlockResult, RayTraceEntityResult, RaycastResult, World, WorldBorder,
};
pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager};

View File

@@ -0,0 +1,689 @@
//! Specialized typed wrappers for major Minecraft mob entities.
//!
//! Provides typed accessors and ergonomic helpers (`Sheep::from_entity`, `Wolf::from_mob`, etc.)
//! without needing dozens of WIT resources.
use std::ops::Deref;
pub use crate::wit::pumpkin::plugin::uuid::Uuid;
pub use crate::wit::pumpkin::plugin::world::{
AgeableData, BlockDirection, CatData, CreeperData, DyeColor, EndermanData, Entity, FoxData,
IronGolemData, LivingEntity, Mob, MobData, SheepData, ShulkerData, SlimeData, VillagerData,
VillagerProfession, WolfData, ZombieData,
};
/// Trait implemented by all specialized mob wrappers to allow generic downcasting via `.cast::<T>()`.
pub trait MobCast<'a>: Sized {
/// Attempts to wrap a [`Mob`] reference if the underlying entity data matches.
fn from_mob(mob: &'a Mob) -> Option<Self>;
/// Attempts to wrap an [`Entity`] reference if it is an AI mob matching this type.
fn from_entity(entity: &'a Entity) -> Option<Self> {
let mob = entity.as_mob()?;
// Note: as_mob() returns a new Resource handle, so we extract via MobData check.
Self::from_mob_owned(mob)
}
/// Attempts to wrap a [`LivingEntity`] reference if it is an AI mob matching this type.
fn from_living(living: &'a LivingEntity) -> Option<Self> {
let mob = living.as_mob()?;
Self::from_mob_owned(mob)
}
#[doc(hidden)]
fn from_mob_owned(mob: Mob) -> Option<Self>;
}
macro_rules! define_mob_wrapper {
(
$(#[$meta:meta])*
$name:ident, $variant:ident, $data_ty:ident
) => {
$(#[$meta])*
pub struct $name<'a> {
mob: &'a Mob,
_owned: Option<Mob>,
}
impl<'a> $name<'a> {
/// Wraps a borrowed [`Mob`] reference if this mob is of the matching type.
#[must_use]
pub fn from_mob(mob: &'a Mob) -> Option<Self> {
if matches!(mob.get_mob_data(), MobData::$variant(_)) {
Some(Self { mob, _owned: None })
} else {
None
}
}
/// Wraps a borrowed [`Entity`] reference if it is an AI mob of the matching type.
#[must_use]
pub fn from_entity(entity: &'a Entity) -> Option<Self> {
let mob = entity.as_mob()?;
Self::from_mob_owned(mob)
}
/// Wraps a borrowed [`LivingEntity`] reference if it is an AI mob of the matching type.
#[must_use]
pub fn from_living(living: &'a LivingEntity) -> Option<Self> {
let mob = living.as_mob()?;
Self::from_mob_owned(mob)
}
fn from_mob_owned(mob: Mob) -> Option<Self> {
if matches!(mob.get_mob_data(), MobData::$variant(_)) {
// Safe reference extension to the owned Mob
let mob_ref = unsafe { &*(&mob as *const Mob) };
Some(Self {
mob: mob_ref,
_owned: Some(mob),
})
} else {
None
}
}
/// Retrieves the underlying data record for this mob.
#[must_use]
pub fn get_data(&self) -> Option<$data_ty> {
match self.mob.get_mob_data() {
MobData::$variant(data) => Some(data),
_ => None,
}
}
/// Updates the underlying data record for this mob.
pub fn set_data(&self, data: $data_ty) -> bool {
self.mob.set_mob_data(MobData::$variant(data))
}
}
impl<'a> Deref for $name<'a> {
type Target = Mob;
fn deref(&self) -> &Self::Target {
self.mob
}
}
impl<'a> TryFrom<&'a Mob> for $name<'a> {
type Error = ();
fn try_from(mob: &'a Mob) -> Result<Self, Self::Error> {
Self::from_mob(mob).ok_or(())
}
}
impl<'a> TryFrom<&'a Entity> for $name<'a> {
type Error = ();
fn try_from(entity: &'a Entity) -> Result<Self, Self::Error> {
Self::from_entity(entity).ok_or(())
}
}
impl<'a> TryFrom<&'a LivingEntity> for $name<'a> {
type Error = ();
fn try_from(living: &'a LivingEntity) -> Result<Self, Self::Error> {
Self::from_living(living).ok_or(())
}
}
impl<'a> MobCast<'a> for $name<'a> {
fn from_mob(mob: &'a Mob) -> Option<Self> {
Self::from_mob(mob)
}
fn from_living(living: &'a LivingEntity) -> Option<Self> {
Self::from_living(living)
}
fn from_mob_owned(mob: Mob) -> Option<Self> {
Self::from_mob_owned(mob)
}
}
};
}
define_mob_wrapper!(
/// Specialized wrapper for Sheep entities.
Sheep, Sheep, SheepData
);
impl<'a> Sheep<'a> {
/// Gets the fleece dye color of this sheep.
#[must_use]
pub fn get_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::White, |d| d.color)
}
/// Sets the fleece dye color of this sheep.
pub fn set_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.color = color;
self.set_data(data);
}
}
/// Returns whether this sheep has been sheared.
#[must_use]
pub fn is_sheared(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sheared)
}
/// Sets whether this sheep is sheared.
pub fn set_sheared(&self, sheared: bool) {
if let Some(mut data) = self.get_data() {
data.is_sheared = sheared;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Wolf entities.
Wolf, Wolf, WolfData
);
impl<'a> Wolf<'a> {
/// Returns whether this wolf is tamed.
#[must_use]
pub fn is_tamed(&self) -> bool {
self.get_data().is_some_and(|d| d.is_tamed)
}
/// Sets whether this wolf is tamed.
pub fn set_tamed(&self, tamed: bool) {
if let Some(mut data) = self.get_data() {
data.is_tamed = tamed;
self.set_data(data);
}
}
/// Gets the UUID of the player who owns this wolf, if any.
#[must_use]
pub fn get_owner(&self) -> Option<Uuid> {
self.get_data().and_then(|d| d.owner)
}
/// Sets the owner of this wolf by UUID.
pub fn set_owner(&self, owner: Option<Uuid>) {
if let Some(mut data) = self.get_data() {
data.owner = owner;
self.set_data(data);
}
}
/// Returns whether this wolf is currently in a sitting pose.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Orders this wolf to sit or stand.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Gets the collar dye color of this wolf.
#[must_use]
pub fn get_collar_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::Red, |d| d.collar_color)
}
/// Sets the collar dye color of this wolf.
pub fn set_collar_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.collar_color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Cat entities.
Cat, Cat, CatData
);
impl<'a> Cat<'a> {
/// Returns whether this cat is tamed.
#[must_use]
pub fn is_tamed(&self) -> bool {
self.get_data().is_some_and(|d| d.is_tamed)
}
/// Sets whether this cat is tamed.
pub fn set_tamed(&self, tamed: bool) {
if let Some(mut data) = self.get_data() {
data.is_tamed = tamed;
self.set_data(data);
}
}
/// Gets the owner UUID of this cat.
#[must_use]
pub fn get_owner(&self) -> Option<Uuid> {
self.get_data().and_then(|d| d.owner)
}
/// Sets the owner UUID of this cat.
pub fn set_owner(&self, owner: Option<Uuid>) {
if let Some(mut data) = self.get_data() {
data.owner = owner;
self.set_data(data);
}
}
/// Returns whether this cat is in a sitting pose.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Orders this cat to sit or stand.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Gets the collar dye color of this cat.
#[must_use]
pub fn get_collar_color(&self) -> DyeColor {
self.get_data().map_or(DyeColor::Red, |d| d.collar_color)
}
/// Sets the collar dye color of this cat.
pub fn set_collar_color(&self, color: DyeColor) {
if let Some(mut data) = self.get_data() {
data.collar_color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Villager entities.
Villager, Villager, VillagerData
);
impl<'a> Villager<'a> {
/// Gets the profession of this villager.
#[must_use]
pub fn get_profession(&self) -> VillagerProfession {
self.get_data()
.map_or(VillagerProfession::None, |d| d.profession)
}
/// Sets the profession of this villager.
pub fn set_profession(&self, profession: VillagerProfession) {
if let Some(mut data) = self.get_data() {
data.profession = profession;
self.set_data(data);
}
}
/// Gets the trading level of this villager (1-5).
#[must_use]
pub fn get_level(&self) -> u8 {
self.get_data().map_or(1, |d| d.level)
}
/// Sets the trading level of this villager (1-5).
pub fn set_level(&self, level: u8) {
if let Some(mut data) = self.get_data() {
data.level = level;
self.set_data(data);
}
}
/// Gets the trading experience points of this villager.
#[must_use]
pub fn get_experience(&self) -> u32 {
self.get_data().map_or(0, |d| d.experience)
}
/// Sets the trading experience points of this villager.
pub fn set_experience(&self, experience: u32) {
if let Some(mut data) = self.get_data() {
data.experience = experience;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Creeper entities.
Creeper, Creeper, CreeperData
);
impl<'a> Creeper<'a> {
/// Returns whether this creeper is charged (struck by lightning).
#[must_use]
pub fn is_powered(&self) -> bool {
self.get_data().is_some_and(|d| d.is_powered)
}
/// Sets whether this creeper is powered/charged.
pub fn set_powered(&self, powered: bool) {
if let Some(mut data) = self.get_data() {
data.is_powered = powered;
self.set_data(data);
}
}
/// Gets the fuse duration of this creeper in ticks.
#[must_use]
pub fn get_fuse(&self) -> i32 {
self.get_data().map_or(30, |d| d.fuse)
}
/// Sets the fuse duration of this creeper in ticks.
pub fn set_fuse(&self, fuse: i32) {
if let Some(mut data) = self.get_data() {
data.fuse = fuse;
self.set_data(data);
}
}
/// Returns whether this creeper has been manually ignited with flint and steel.
#[must_use]
pub fn is_ignited(&self) -> bool {
self.get_data().is_some_and(|d| d.is_ignited)
}
/// Sets whether this creeper is ignited.
pub fn set_ignited(&self, ignited: bool) {
if let Some(mut data) = self.get_data() {
data.is_ignited = ignited;
self.set_data(data);
}
}
/// Gets the explosion radius of this creeper.
#[must_use]
pub fn get_explosion_radius(&self) -> u8 {
self.get_data().map_or(3, |d| d.explosion_radius)
}
/// Sets the explosion radius of this creeper.
pub fn set_explosion_radius(&self, radius: u8) {
if let Some(mut data) = self.get_data() {
data.explosion_radius = radius;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Slime and Magma Cube entities.
Slime, Slime, SlimeData
);
impl<'a> Slime<'a> {
/// Gets the size scale of this slime.
#[must_use]
pub fn get_size(&self) -> i32 {
self.get_data().map_or(1, |d| d.size)
}
/// Sets the size scale of this slime.
pub fn set_size(&self, size: i32) {
if let Some(mut data) = self.get_data() {
data.size = size;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Enderman entities.
Enderman, Enderman, EndermanData
);
impl<'a> Enderman<'a> {
/// Gets the numerical block state ID carried by this enderman, if any.
#[must_use]
pub fn get_carried_block(&self) -> Option<u16> {
self.get_data().and_then(|d| d.carried_block_state)
}
/// Sets the carried block state ID for this enderman.
pub fn set_carried_block(&self, block_state: Option<u16>) {
if let Some(mut data) = self.get_data() {
data.carried_block_state = block_state;
self.set_data(data);
}
}
/// Returns whether this enderman is currently screaming (angry).
#[must_use]
pub fn is_screaming(&self) -> bool {
self.get_data().is_some_and(|d| d.is_screaming)
}
/// Returns whether this enderman is staring at a player.
#[must_use]
pub fn is_staring(&self) -> bool {
self.get_data().is_some_and(|d| d.is_staring)
}
}
define_mob_wrapper!(
/// Specialized wrapper for Iron Golem entities.
IronGolem, IronGolem, IronGolemData
);
impl<'a> IronGolem<'a> {
/// Returns whether this iron golem was created by a player.
#[must_use]
pub fn is_player_created(&self) -> bool {
self.get_data().is_some_and(|d| d.is_player_created)
}
/// Sets whether this iron golem is considered player-created.
pub fn set_player_created(&self, created: bool) {
if let Some(mut data) = self.get_data() {
data.is_player_created = created;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Fox entities.
Fox, Fox, FoxData
);
impl<'a> Fox<'a> {
/// Returns whether this fox is sitting.
#[must_use]
pub fn is_sitting(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sitting)
}
/// Sets whether this fox is sitting.
pub fn set_sitting(&self, sitting: bool) {
if let Some(mut data) = self.get_data() {
data.is_sitting = sitting;
self.set_data(data);
}
}
/// Returns whether this fox is sleeping.
#[must_use]
pub fn is_sleeping(&self) -> bool {
self.get_data().is_some_and(|d| d.is_sleeping)
}
/// Sets whether this fox is sleeping.
pub fn set_sleeping(&self, sleeping: bool) {
if let Some(mut data) = self.get_data() {
data.is_sleeping = sleeping;
self.set_data(data);
}
}
/// Returns whether this fox is crouching.
#[must_use]
pub fn is_crouching(&self) -> bool {
self.get_data().is_some_and(|d| d.is_crouching)
}
/// Sets whether this fox is crouching.
pub fn set_crouching(&self, crouching: bool) {
if let Some(mut data) = self.get_data() {
data.is_crouching = crouching;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Shulker entities.
Shulker, Shulker, ShulkerData
);
impl<'a> Shulker<'a> {
/// Gets the attached block direction of this shulker.
#[must_use]
pub fn get_attached_face(&self) -> BlockDirection {
self.get_data()
.map_or(BlockDirection::Down, |d| d.attached_face)
}
/// Sets the attached block direction of this shulker.
pub fn set_attached_face(&self, face: BlockDirection) {
if let Some(mut data) = self.get_data() {
data.attached_face = face;
self.set_data(data);
}
}
/// Gets the raw peek amount of this shulker (0-100).
#[must_use]
pub fn get_peek_amount(&self) -> u8 {
self.get_data().map_or(0, |d| d.peek_amount)
}
/// Sets the raw peek amount of this shulker (0-100).
pub fn set_peek_amount(&self, amount: u8) {
if let Some(mut data) = self.get_data() {
data.peek_amount = amount;
self.set_data(data);
}
}
/// Gets the custom dye color of this shulker, if dyed.
#[must_use]
pub fn get_color(&self) -> Option<DyeColor> {
self.get_data().and_then(|d| d.color)
}
/// Sets or removes the custom dye color of this shulker.
pub fn set_color(&self, color: Option<DyeColor>) {
if let Some(mut data) = self.get_data() {
data.color = color;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Zombie entities.
Zombie, Zombie, ZombieData
);
impl<'a> Zombie<'a> {
/// Returns whether this zombie is a baby.
#[must_use]
pub fn is_baby(&self) -> bool {
self.get_data().is_some_and(|d| d.is_baby)
}
/// Sets whether this zombie is a baby.
pub fn set_baby(&self, baby: bool) {
if let Some(mut data) = self.get_data() {
data.is_baby = baby;
self.set_data(data);
}
}
/// Returns whether this zombie is capable of breaking doors.
#[must_use]
pub fn can_break_doors(&self) -> bool {
self.get_data().is_some_and(|d| d.can_break_doors)
}
/// Sets whether this zombie can break doors.
pub fn set_can_break_doors(&self, can_break: bool) {
if let Some(mut data) = self.get_data() {
data.can_break_doors = can_break;
self.set_data(data);
}
}
}
define_mob_wrapper!(
/// Specialized wrapper for Ageable animal mobs (cows, pigs, chickens, rabbits, etc.).
Ageable, Ageable, AgeableData
);
impl<'a> Ageable<'a> {
/// Returns whether this animal is a baby.
#[must_use]
pub fn is_baby(&self) -> bool {
self.get_data().is_some_and(|d| d.is_baby)
}
/// Sets whether this animal is a baby.
pub fn set_baby(&self, baby: bool) {
if let Some(mut data) = self.get_data() {
data.is_baby = baby;
self.set_data(data);
}
}
/// Gets the age of this animal in ticks (negative for babies).
#[must_use]
pub fn get_age(&self) -> i32 {
self.get_data().map_or(0, |d| d.age)
}
/// Sets the age of this animal in ticks.
pub fn set_age(&self, age: i32) {
if let Some(mut data) = self.get_data() {
data.age = age;
self.set_data(data);
}
}
}
/// Extension trait providing generic `.cast::<T>()` downcasting on [`Entity`] and [`Mob`].
pub trait EntityCastExt {
/// Attempts to cast this entity or mob reference to a specialized mob wrapper type.
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T>;
}
impl EntityCastExt for Mob {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_mob(self)
}
}
impl EntityCastExt for Entity {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_entity(self)
}
}
impl EntityCastExt for LivingEntity {
fn cast<'a, T: MobCast<'a>>(&'a self) -> Option<T> {
T::from_living(self)
}
}

View File

@@ -55,7 +55,6 @@ tokio-util = { workspace = true, features = ["rt"] }
rand.workspace = true
rustc-hash.workspace = true
slotmap.workspace = true
crossfire.workspace = true
[dev-dependencies]
tempfile.workspace = true

View File

@@ -73,11 +73,8 @@ fn bench_chunk_deserialization(c: &mut Criterion) {
let Chunk::Level(chunk) = chunk else {
panic!("full generation must return a level chunk");
};
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.expect("failed to create benchmark runtime");
let bytes = runtime
.block_on(chunk.to_bytes())
let bytes = chunk
.to_bytes()
.expect("failed to serialize benchmark chunk");
let position = Vector2::new(chunk.x, chunk.z);

View File

@@ -8,7 +8,6 @@ use std::{
io::{Read, SeekFrom, Write},
marker::PhantomData,
path::{Path, PathBuf},
pin::Pin,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::{
@@ -308,7 +307,7 @@ impl AnvilChunkData {
}
}
async fn from_chunk<S>(
fn from_chunk<S>(
chunk: &S,
compression: Option<Compression>,
chunk_config: &AnvilChunkConfig,
@@ -318,7 +317,6 @@ impl AnvilChunkData {
{
let raw_bytes = chunk
.to_bytes()
.await
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
let compression = compression.unwrap_or_else(|| chunk_config.compression.algorithm.into());
@@ -498,9 +496,7 @@ impl<S: SingleChunkDataSerializer> Default for AnvilChunkFile<S> {
}
pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable + 'static {
fn to_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>;
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError>;
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError>;
fn position(&self) -> (i32, i32);
}
@@ -620,8 +616,7 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
let compression_type = self.chunks_data[index]
.as_ref()
.and_then(|chunk_data| chunk_data.serialized_data.compression);
let new_chunk_data =
AnvilChunkData::from_chunk(chunk, compression_type, chunk_config).await?;
let new_chunk_data = AnvilChunkData::from_chunk(chunk, compression_type, chunk_config)?;
let mut write_action = self.write_action.lock().await;
if !chunk_config.write_in_place {

View File

@@ -572,7 +572,6 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for LinearV2File<S>
let index = Self::get_chunk_index(chunk.position().0, chunk.position().1);
let chunk_raw: Bytes = chunk
.to_bytes()
.await
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
self.timestamps[index] = SystemTime::now()

View File

@@ -1,6 +1,5 @@
use std::{
path::PathBuf,
pin::Pin,
str::FromStr,
sync::{
RwLock,
@@ -13,7 +12,6 @@ use pumpkin_data::{Block, BlockStateId, chunk::ChunkStatus, fluid::Fluid};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation};
use rustc_hash::FxHashMap;
use tokio::sync::Mutex;
use crate::{
chunk::{
@@ -43,10 +41,8 @@ impl SingleChunkDataSerializer for ChunkData {
}
#[inline]
fn to_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
Box::pin(async move { Ok(self.internal_to_bytes()) })
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
Ok(self.internal_to_bytes())
}
#[inline]
@@ -707,10 +703,8 @@ impl SingleChunkDataSerializer for ChunkEntityData {
}
#[inline]
fn to_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
Box::pin(async move { self.internal_to_bytes().await })
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
Ok(self.internal_to_bytes())
}
#[inline]
@@ -775,12 +769,12 @@ impl ChunkEntityData {
Ok(Self {
x: position.x,
z: position.y,
data: Mutex::new(entities),
data: std::sync::Mutex::new(entities),
dirty: AtomicBool::new(false),
})
}
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
fn internal_to_bytes(&self) -> Bytes {
let mut root = NbtCompound::new();
root.put_int("DataVersion", WORLD_DATA_VERSION);
root.put(
@@ -790,14 +784,14 @@ impl ChunkEntityData {
let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self
.data
.lock()
.await
.unwrap_or_else(std::sync::PoisonError::into_inner)
.iter()
.map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone()))
.collect();
root.put_list("Entities", entities_tag);
let nbt = pumpkin_nbt::Nbt::from(root);
Ok(nbt.write())
nbt.write()
}
}

View File

@@ -108,7 +108,6 @@ where
let bytes = chunk_data
.to_bytes()
.await
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest);
@@ -165,9 +164,6 @@ mod tests {
use crate::chunk::io::{ChunkSerializer, LoadedData};
use bytes::Bytes;
use pumpkin_util::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::pin::Pin;
use tempfile::TempDir;
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -185,17 +181,14 @@ mod tests {
}
impl SingleChunkDataSerializer for MockChunk {
fn to_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>
{
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
let mut root = pumpkin_nbt::compound::NbtCompound::new();
root.put_int("x", self.x);
root.put_int("z", self.z);
let i8_vec: Vec<i8> = self.data.iter().map(|&b| b as i8).collect();
root.put("data", pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into()));
let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed();
Box::pin(async move { Ok(bytes) })
Ok(bytes)
}
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError> {
let mut cursor = std::io::Cursor::new(bytes);

View File

@@ -13,10 +13,7 @@ use tokio::{
use tracing::{debug, error, trace};
use crate::{
chunk::{
ChunkReadingError, ChunkWritingError,
io::{BoxFuture, Dirtiable},
},
chunk::{ChunkReadingError, ChunkWritingError, io::Dirtiable},
level::LevelFolder,
};
@@ -201,230 +198,209 @@ where
{
type Data = Arc<S::Data>;
fn watch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut watchers = self.watchers.write().await;
for path in paths {
*watchers.entry(path).or_insert(0) += 1;
}
})
let mut watchers = self.watchers.write().await;
for path in paths {
*watchers.entry(path).or_insert(0) += 1;
}
}
fn unwatch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut paths_to_evict = Vec::new();
{
let mut watchers = self.watchers.write().await;
for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut e) =
watchers.entry(path)
{
let count = e.get_mut();
*count = count.saturating_sub(1);
if *count == 0 {
let (path, _) = e.remove_entry();
paths_to_evict.push(path);
}
let mut paths_to_evict = Vec::new();
{
let mut watchers = self.watchers.write().await;
for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) {
let count = e.get_mut();
*count = count.saturating_sub(1);
if *count == 0 {
let (path, _) = e.remove_entry();
paths_to_evict.push(path);
}
}
}
}
for path in paths_to_evict {
self.maybe_evict(&path).await;
}
})
for path in paths_to_evict {
self.maybe_evict(&path).await;
}
}
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
let paths: Vec<PathBuf> = {
let mut watchers = self.watchers.write().await;
let keys: Vec<_> = watchers.keys().cloned().collect();
watchers.clear();
keys
};
for path in paths {
self.maybe_evict(&path).await;
}
})
async fn clear_watched_chunks(&self) {
let paths: Vec<PathBuf> = {
let mut watchers = self.watchers.write().await;
let keys: Vec<_> = watchers.keys().cloned().collect();
watchers.clear();
keys
};
for path in paths {
self.maybe_evict(&path).await;
}
}
fn fetch_chunks<'a>(
async fn fetch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>],
stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> {
Box::pin(async move {
// Group requested chunk coords by their region file.
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
for at in chunk_coords {
regions_chunks
.entry(S::get_chunk_key(at))
.or_default()
.push(*at);
) {
// Group requested chunk coords by their region file.
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
for at in chunk_coords {
regions_chunks
.entry(S::get_chunk_key(at))
.or_default()
.push(*at);
}
let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| {
let task_stream = stream.clone();
async move {
let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return;
}
Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return;
}
};
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) = mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
// Forward received chunks, wrapping them in `Arc`.
// Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await {
let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped).await.is_err() {
// Receiver dropped; abort early to avoid wasted work.
return;
}
}
};
// Hold the read lock only for the duration of `get_chunks`.
let read = async move {
let serializer = chunk_serializer.read().await;
serializer.get_chunks(chunks, send).await;
};
join!(forward, read);
// Evict if not watched and references are dropped
self.maybe_evict(&path).await;
}
});
let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| {
let task_stream = stream.clone();
async move {
let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return;
}
Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return;
}
};
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) =
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
// Forward received chunks, wrapping them in `Arc`.
// Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await {
let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped).await.is_err() {
// Receiver dropped; abort early to avoid wasted work.
return;
}
}
};
// Hold the read lock only for the duration of `get_chunks`.
let read = async move {
let serializer = chunk_serializer.read().await;
serializer.get_chunks(chunks, send).await;
};
join!(forward, read);
// Evict if not watched and references are dropped
self.maybe_evict(&path).await;
}
});
join_all(region_tasks).await;
})
join_all(region_tasks).await;
}
fn save_chunks<'a>(
async fn save_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
Box::pin(async move {
// Group chunks by region file.
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
for (at, chunk) in chunks_data {
regions_chunks
.entry(S::get_chunk_key(&at))
.or_default()
.push(chunk);
}
) -> Result<(), ChunkWritingError> {
// Group chunks by region file.
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
for (at, chunk) in chunks_data {
regions_chunks
.entry(S::get_chunk_key(&at))
.or_default()
.push(chunk);
}
let tasks = regions_chunks
.into_iter()
.map(|(file_name, chunk_locks)| async move {
let path = P::file_path(folder, &file_name);
trace!("Saving chunks into {}", path.display());
let tasks = regions_chunks
.into_iter()
.map(|(file_name, chunk_locks)| async move {
let path = P::file_path(folder, &file_name);
trace!("Saving chunks into {}", path.display());
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return Err(ChunkWritingError::IoError(std::io::Error::other(
"get_serializer returned ChunkNotExist",
)));
}
Err(ChunkReadingError::IoError(err)) => {
error!("I/O error reading region before write: {err}");
return Err(ChunkWritingError::IoError(err));
}
Err(err) => {
return Err(ChunkWritingError::IoError(std::io::Error::other(
err.to_string(),
)));
}
};
let chunk_serializer = match self.get_serializer(&path).await {
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
return Err(ChunkWritingError::IoError(std::io::Error::other(
"get_serializer returned ChunkNotExist",
)));
}
Err(ChunkReadingError::IoError(err)) => {
error!("I/O error reading region before write: {err}");
return Err(ChunkWritingError::IoError(err));
}
Err(err) => {
return Err(ChunkWritingError::IoError(std::io::Error::other(
err.to_string(),
)));
}
};
{
let mut writer = chunk_serializer.write().await;
for chunk in &chunk_locks {
// Atomically snapshot and clear the dirty flag before we
// write so that any mutation that races in *during* this
// serialisation round will mark dirty again correctly.
let was_dirty = chunk.is_dirty();
chunk.mark_dirty(false);
if was_dirty {
writer.update_chunk(&**chunk, &self.chunk_config).await?;
}
}
// Write-lock released here — flush can proceed under a read-lock.
}
trace!("Chunk data updated for {}", path.display());
// We check watchers *after* releasing the write-lock to honour
// lock ordering (serializer lock → watchers, never the reverse).
let is_watched = {
let watchers = self.watchers.read().await;
watchers.get(&path).is_some_and(|&c| c > 0)
};
if !is_watched {
// A read-lock suffices for `write()` since we have already
// applied all mutations above.
{
let mut writer = chunk_serializer.write().await;
for chunk in &chunk_locks {
// Atomically snapshot and clear the dirty flag before we
// write so that any mutation that races in *during* this
// serialisation round will mark dirty again correctly.
let was_dirty = chunk.is_dirty();
chunk.mark_dirty(false);
if was_dirty {
writer.update_chunk(&**chunk, &self.chunk_config).await?;
}
}
// Write-lock released here — flush can proceed under a read-lock.
}
trace!("Chunk data updated for {}", path.display());
// We check watchers *after* releasing the write-lock to honour
// lock ordering (serializer lock → watchers, never the reverse).
let is_watched = {
let watchers = self.watchers.read().await;
watchers.get(&path).is_some_and(|&c| c > 0)
let serializer = chunk_serializer.read().await;
debug!("Flushing {} to disk", path.display());
serializer
.write(&path)
.await
.map_err(ChunkWritingError::IoError)?;
// Read-lock released here.
};
if !is_watched {
// A read-lock suffices for `write()` since we have already
// applied all mutations above.
{
let serializer = chunk_serializer.read().await;
debug!("Flushing {} to disk", path.display());
serializer
.write(&path)
.await
.map_err(ChunkWritingError::IoError)?;
// Read-lock released here.
};
// Drop our handle so `can_remove` may succeed.
drop(chunk_serializer);
// Drop our handle so `can_remove` may succeed.
drop(chunk_serializer);
// Evict the cache entry when no longer needed.
self.maybe_evict(&path).await;
}
// Evict the cache entry when no longer needed.
self.maybe_evict(&path).await;
}
Ok(())
});
Ok(())
});
// Collect all region results; surface the first error encountered.
let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
results.into_iter().find(Result::is_err).unwrap_or(Ok(()))
})
// Collect all region results; surface the first error encountered.
let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
results.into_iter().find(Result::is_err).unwrap_or(Ok(()))
}
/// Blocks until all in-flight serialiser operations have completed by
@@ -433,27 +409,25 @@ where
///
/// This is a linearisation point: after this future resolves no mutation
/// started before the call is still running.
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
// Snapshot the current set of loaders under a read-lock so we do
// not block new insertions longer than necessary.
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
{ self.file_locks.read().await.values().cloned().collect() };
async fn block_and_await_ongoing_tasks(&self) {
// Snapshot the current set of loaders under a read-lock so we do
// not block new insertions longer than necessary.
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
{ self.file_locks.read().await.values().cloned().collect() };
// For each loader that has been initialised, acquire a write-lock
// and release it immediately. This guarantees that any concurrent
// read or write operation that was in progress has finished.
let drain_tasks = loaders.into_iter().map(|loader| async move {
if let Some(serializer_arc) = loader.internal.get() {
// Acquiring + immediately dropping the write-lock acts as a
// barrier: it can only succeed once all current lock holders
// have released their guards.
let _guard = serializer_arc.write().await;
}
});
// For each loader that has been initialised, acquire a write-lock
// and release it immediately. This guarantees that any concurrent
// read or write operation that was in progress has finished.
let drain_tasks = loaders.into_iter().map(|loader| async move {
if let Some(serializer_arc) = loader.internal.get() {
// Acquiring + immediately dropping the write-lock acts as a
// barrier: it can only succeed once all current lock holders
// have released their guards.
let _guard = serializer_arc.write().await;
}
});
join_all(drain_tasks).await;
})
join_all(drain_tasks).await;
}
}
@@ -480,68 +454,60 @@ where
{
type Data = Arc<P>;
fn fetch_chunks<'a>(
async fn fetch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> {
) {
match self {
Self::Linear(io) => io.fetch_chunks(folder, chunk_coords, stream),
Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream),
Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream),
Self::Linear(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
}
}
fn save_chunks<'a>(
async fn save_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
) -> Result<(), ChunkWritingError> {
match self {
Self::Linear(io) => io.save_chunks(folder, chunks_data),
Self::Anvil(io) => io.save_chunks(folder, chunks_data),
Self::Pump(io) => io.save_chunks(folder, chunks_data),
Self::Linear(io) => io.save_chunks(folder, chunks_data).await,
Self::Anvil(io) => io.save_chunks(folder, chunks_data).await,
Self::Pump(io) => io.save_chunks(folder, chunks_data).await,
}
}
fn watch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
match self {
Self::Linear(io) => io.watch_chunks(folder, chunks),
Self::Anvil(io) => io.watch_chunks(folder, chunks),
Self::Pump(io) => io.watch_chunks(folder, chunks),
Self::Linear(io) => io.watch_chunks(folder, chunks).await,
Self::Anvil(io) => io.watch_chunks(folder, chunks).await,
Self::Pump(io) => io.watch_chunks(folder, chunks).await,
}
}
fn unwatch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
match self {
Self::Linear(io) => io.unwatch_chunks(folder, chunks),
Self::Anvil(io) => io.unwatch_chunks(folder, chunks),
Self::Pump(io) => io.unwatch_chunks(folder, chunks),
Self::Linear(io) => io.unwatch_chunks(folder, chunks).await,
Self::Anvil(io) => io.unwatch_chunks(folder, chunks).await,
Self::Pump(io) => io.unwatch_chunks(folder, chunks).await,
}
}
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
async fn clear_watched_chunks(&self) {
match self {
Self::Linear(io) => io.clear_watched_chunks(),
Self::Anvil(io) => io.clear_watched_chunks(),
Self::Pump(io) => io.clear_watched_chunks(),
Self::Linear(io) => io.clear_watched_chunks().await,
Self::Anvil(io) => io.clear_watched_chunks().await,
Self::Pump(io) => io.clear_watched_chunks().await,
}
}
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
async fn block_and_await_ongoing_tasks(&self) {
match self {
Self::Linear(io) => io.block_and_await_ongoing_tasks(),
Self::Anvil(io) => io.block_and_await_ongoing_tasks(),
Self::Pump(io) => io.block_and_await_ongoing_tasks(),
Self::Linear(io) => io.block_and_await_ongoing_tasks().await,
Self::Anvil(io) => io.block_and_await_ongoing_tasks().await,
Self::Pump(io) => io.block_and_await_ongoing_tasks().await,
}
}
}

View File

@@ -1,4 +1,4 @@
use std::{error, pin::Pin};
use std::error;
use bytes::Bytes;
use pumpkin_util::math::vector2::Vector2;
@@ -38,8 +38,6 @@ pub trait Dirtiable {
fn mark_dirty(&self, flag: bool);
}
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
/// Trait to handle the IO of chunks
/// for loading and saving chunks data
/// can be implemented for different types of IO
@@ -59,34 +57,34 @@ where
folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()>; // Returns BoxFuture<()>
) -> impl Future<Output = ()> + Send + 'a;
/// Persist the chunks data
fn save_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>>; // Returns BoxFuture<Result>
) -> impl Future<Output = Result<(), ChunkWritingError>> + Send + 'a;
/// Tells the `ChunkIO` that these chunks are currently loaded in memory
fn watch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()>;
) -> impl Future<Output = ()> + Send + 'a;
/// Tells the `ChunkIO` that these chunks are no longer loaded in memory
fn unwatch_chunks<'a>(
&'a self,
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()>;
) -> impl Future<Output = ()> + Send + 'a;
/// Tells the `ChunkIO` that no more chunks are loaded in memory
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()>;
fn clear_watched_chunks(&self) -> impl Future<Output = ()> + Send + '_;
/// Ensure that all ongoing operations are finished
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()>;
fn block_and_await_ongoing_tasks(&self) -> impl Future<Output = ()> + Send + '_;
}
/// Trait to serialize and deserialize the chunk data to and from bytes.

View File

@@ -14,7 +14,6 @@ use std::sync::RwLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use thiserror::Error;
use tokio::sync::Mutex;
pub mod format;
pub mod io;
@@ -90,7 +89,7 @@ pub struct ChunkEntityData {
pub x: i32,
/// Chunk Z
pub z: i32,
pub data: Mutex<Vec<NbtCompound>>,
pub data: std::sync::Mutex<Vec<NbtCompound>>,
pub dirty: AtomicBool,
}

View File

@@ -3,7 +3,7 @@ use super::chunk_holder::ChunkHolder;
use super::chunk_state::{Chunk, StagedChunkEnum};
use super::dag::{DAG, EdgeKey, Node, NodeKey};
use super::generation_cache::Cache;
use super::worker_logic::{RecvChunk, generation_work, io_read_work, io_write_work};
use super::worker_logic::{RecvChunk, io_read_work, io_write_work};
use super::{
ChunkLevel, ChunkListener, ChunkLoading, ChunkPos, HashMapType, HashSetType, IOLock,
LevelChannel,
@@ -69,12 +69,10 @@ pub struct GenerationSchedule {
running_task_count: u16,
max_in_flight: u16,
queue_dirty: bool,
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>,
io_read: crossfire::compat::MTx<Vec<ChunkPos>>,
io_write: crossfire::compat::Tx<Vec<(ChunkPos, Chunk)>>,
generate: crossfire::compat::MTx<(ChunkPos, Cache, StagedChunkEnum)>,
send_chunk: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
recv_chunk: crossbeam::channel::Receiver<(ChunkPos, RecvChunk)>,
io_read: tokio::sync::mpsc::Sender<Vec<ChunkPos>>,
io_write: tokio::sync::mpsc::Sender<Vec<(ChunkPos, Chunk)>>,
send_chunk: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
listener: Arc<ChunkListener>,
lighting_config: LightingEngineConfig,
last_unload: std::time::Instant,
@@ -83,22 +81,17 @@ pub struct GenerationSchedule {
impl GenerationSchedule {
pub fn create(
io_read_thread_count: usize,
gen_thread_count: usize,
level: Arc<Level>,
level_channel: Arc<LevelChannel>,
listener: Arc<ChunkListener>,
thread_tracker: &mut Vec<thread::JoinHandle<()>>,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) {
let (send_chunk, recv_chunk) = crossfire::compat::mpmc::unbounded_blocking();
let (send_chunk, recv_chunk) = crossbeam::channel::unbounded();
let (send_read_io, recv_read_io) =
crossfire::compat::mpmc::bounded_tx_blocking_rx_async(io_read_thread_count + 5);
let (send_read_io, recv_read_io) = tokio::sync::mpsc::channel(io_read_thread_count + 5);
let recv_read_io = Arc::new(tokio::sync::Mutex::new(recv_read_io));
let (send_write_io, recv_write_io) =
crossfire::compat::spsc::bounded_tx_blocking_rx_async(500);
let (send_gen, recv_gen) = crossfire::compat::mpmc::bounded_blocking(gen_thread_count + 5);
let (send_write_io, recv_write_io) = tokio::sync::mpsc::channel(500);
let io_lock = Arc::new((
Mutex::new(HashMapType::default()),
@@ -120,28 +113,8 @@ impl GenerationSchedule {
io_lock.clone(),
));
if gen_pool.is_none() {
for i in 0..gen_thread_count {
let recv_gen = recv_gen.clone();
let send_chunk = send_chunk.clone();
let level_clone = level.clone();
let handle = thread::Builder::new()
.name(format!("Gen-{i}"))
.spawn(move || {
generation_work(&recv_gen, &send_chunk, &level_clone);
})
.expect("Failed to spawn Generation Thread");
thread_tracker.push(handle);
}
}
let max_in_flight = if gen_pool.is_some() {
(thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16
} else {
gen_thread_count as u16
};
let max_in_flight =
(thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16;
let level_sched = level;
let lighting_config = level_sched.lighting_config;
@@ -164,9 +137,7 @@ impl GenerationSchedule {
recv_chunk,
io_read: send_read_io,
io_write: send_write_io,
generate: send_gen,
send_chunk,
gen_pool,
listener,
chunk_map: HashMap::default(),
lighting_config,
@@ -446,8 +417,12 @@ impl GenerationSchedule {
})
});
if all_ready {
now_ready.push(node_key);
false
if node.in_degree == 0 {
now_ready.push(node_key);
false
} else {
true
}
} else {
true
}
@@ -463,7 +438,6 @@ impl GenerationSchedule {
Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage);
self.queue.push(TaskHeapNode(priority, node_key));
}
// If in_degree > 0, drop_node will re-queue when unblocked
}
}
@@ -796,7 +770,7 @@ impl GenerationSchedule {
*data.entry(*pos).or_insert(0) += 1;
}
drop(data);
if let Err(e) = self.io_write.send(chunks) {
if let Err(e) = self.io_write.blocking_send(chunks) {
error!(
"Failed to send chunks to io write thread during save (may have shut down): {:?}",
e
@@ -851,7 +825,7 @@ impl GenerationSchedule {
}
drop(data);
if let Err(e) = self.io_write.send(chunks) {
if let Err(e) = self.io_write.blocking_send(chunks) {
error!("Failed to send chunks to io write thread: {:?}", e);
}
}
@@ -1311,7 +1285,10 @@ impl GenerationSchedule {
io_batch.push(node.pos);
if io_batch.len() >= 16
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err()
&& self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
@@ -1320,7 +1297,10 @@ impl GenerationSchedule {
} else {
// Send any pending IO batch before starting generation
if !io_batch.is_empty()
&& self.io_read.send(std::mem::take(&mut io_batch)).is_err()
&& self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
@@ -1432,41 +1412,37 @@ impl GenerationSchedule {
}
self.running_task_count += 1;
if let Some(pool) = &self.gen_pool {
let pos = node.pos;
let stage = node.stage;
let send_chunk = self.send_chunk.clone();
let level = level.clone();
let settings = GenerationSettings::from_dimension(
level.world_gen.load().dimension(),
);
let pos = node.pos;
let stage = node.stage;
let send_chunk = self.send_chunk.clone();
let level = level.clone();
let settings =
GenerationSettings::from_dimension(level.world_gen.load().dimension());
pool.spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation(
pos, cache, stage, &level, settings,
);
let _ = send_chunk.send((pos, result));
});
} else if self.generate.send((node.pos, cache, node.stage)).is_err() {
self.running_task_count = self.running_task_count.saturating_sub(1);
info!("Generation thread closed, saving remaining chunks...");
self.save_all_chunk(true);
break 'out2;
}
rayon::spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation(
pos, cache, stage, &level, settings,
);
let _ = send_chunk.send((pos, result));
});
}
}
}
// Flush any remaining IO batch
if !io_batch.is_empty() && self.io_read.send(std::mem::take(&mut io_batch)).is_err() {
if !io_batch.is_empty()
&& self
.io_read
.blocking_send(std::mem::take(&mut io_batch))
.is_err()
{
info!("IO read thread closed, saving remaining chunks...");
self.save_all_chunk(true);
}
// 3. If queue is empty, wait for work or results
// 5. Wait for work or results
if self.queue.is_empty() {
// If we have tasks in flight, wait for them with timeout
if self.running_task_count > 0 || !self.waiting_for_chunks.is_empty() {
if self.running_task_count > 0 {
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => {
self.receive_chunk(pos, data);
@@ -1474,16 +1450,17 @@ impl GenerationSchedule {
self.garbage_collect_dependencies();
}
}
Err(crossfire::compat::RecvTimeoutError::Timeout) => {
Err(crossbeam::channel::RecvTimeoutError::Timeout) => {
// Periodically check LevelChannel for new requests
if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
}
Err(crossfire::compat::RecvTimeoutError::Disconnected) => break,
Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
}
} else {
// No tasks in flight, wait indefinitely for LevelChannel changes
// No tasks in flight, check for any unblocked waiting tasks or stranded ready tasks
self.check_waiting_tasks();
let restored = Self::restore_ready_tasks(
&mut self.graph,
&mut self.queue,
@@ -1492,10 +1469,14 @@ impl GenerationSchedule {
&self.last_high_priority,
&self.waiting_for_chunks,
);
if restored > 0 {
if restored > 0 || !self.queue.is_empty() {
debug!(
"Restored {restored} stranded ready chunk tasks to generation queue"
);
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
continue;
}
debug_assert!(self.debug_check());
@@ -1508,6 +1489,27 @@ impl GenerationSchedule {
self.sort_queue();
self.queue_dirty = false;
}
} else if self.running_task_count >= self.max_in_flight {
// Queue has tasks, but we are at maximum in-flight capacity.
// Wait for an in-flight worker to finish instead of busy-spinning.
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => {
self.receive_chunk(pos, data);
if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
}
Err(crossbeam::channel::RecvTimeoutError::Timeout) => {
if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
}
Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
}
if self.queue_dirty {
self.sort_queue();
self.queue_dirty = false;
}
}
}
info!(

View File

@@ -6,7 +6,6 @@ use crate::chunk::format::LightContainer;
use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::io::{FileIO, LoadedData};
use crate::level::Level;
use crossfire::compat::AsyncRx;
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::chunk::ChunkStatus;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
@@ -57,19 +56,15 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: LightingEngineConfi
}
async fn load_proto_chunk(chunk: &Arc<crate::chunk::ChunkData>, level: &Level) -> ProtoChunk {
if let Some(pool) = &level.gen_pool {
let (tx, rx) = tokio::sync::oneshot::channel();
let world_gen = level.world_gen.load();
let chunk_clone = chunk.clone();
pool.spawn(move || {
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
let _ = tx.send(p);
});
rx.await
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
} else {
ProtoChunk::from_chunk_data(chunk, &level.world_gen.load())
}
let (tx, rx) = tokio::sync::oneshot::channel();
let world_gen = level.world_gen.load();
let chunk_clone = chunk.clone();
rayon::spawn(move || {
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
let _ = tx.send(p);
});
rx.await
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
}
async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk {
@@ -91,10 +86,7 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
proto.light.block_light = (0..section_count)
.map(|_| LightContainer::new_empty(0))
.collect();
// Set stage to Features
proto.stage = StagedChunkEnum::Features;
Chunk::Proto(Box::new(proto))
} else {
Chunk::Level(chunk)
@@ -106,15 +98,22 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
}
pub async fn io_read_work(
recv: crossfire::compat::MAsyncRx<Vec<ChunkPos>>,
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
recv: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Vec<ChunkPos>>>>,
send: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
level: Arc<Level>,
lock: IOLock,
) {
debug!("io read thread start");
// Cleaner loop and async recv
while let Ok(batch) = recv.recv().await {
loop {
let batch = {
let mut lock_rx = recv.lock().await;
lock_rx.recv().await
};
let Some(batch) = batch else {
break;
};
for pos in &batch {
// Lock handling
loop {
@@ -178,10 +177,14 @@ pub async fn io_read_work(
debug!("io read thread stop");
}
pub async fn io_write_work(recv: AsyncRx<Vec<(ChunkPos, Chunk)>>, level: Arc<Level>, lock: IOLock) {
pub async fn io_write_work(
mut recv: tokio::sync::mpsc::Receiver<Vec<(ChunkPos, Chunk)>>,
level: Arc<Level>,
lock: IOLock,
) {
loop {
// Don't check cancel_token here (keep saving chunks)
let Ok(data) = recv.recv().await else { break };
let Some(data) = recv.recv().await else { break };
// debug!("io write thread receive chunks size {}", data.len());
let mut vec = Vec::with_capacity(data.len());
let mut positions = Vec::with_capacity(data.len());
@@ -287,23 +290,3 @@ pub fn run_generation(
}
}
}
pub fn generation_work(
recv: &crossfire::compat::MRx<(ChunkPos, Cache, StagedChunkEnum)>,
send: &crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
level: &Arc<Level>,
) {
let settings = GenerationSettings::from_dimension(level.world_gen.load().dimension());
loop {
let Ok((pos, cache, stage)) = recv.recv() else {
debug!("generation channel closed, exiting");
break;
};
let result = run_generation(pos, cache, stage, level, settings);
if send.send((pos, result)).is_err() {
break;
}
}
}

View File

@@ -11,7 +11,6 @@ pub fn into_level(
level_config: &LevelConfig,
base_directory: PathBuf,
seed: i64,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Level> {
Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool)
Level::from_root_folder(level_config, base_directory, seed, dimension)
}

View File

@@ -114,7 +114,6 @@ pub struct Level {
pub level_channel: Arc<LevelChannel>,
pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>,
pub chunk_listener: Arc<ChunkListener>,
pub gen_pool: Option<Arc<rayon::ThreadPool>>,
}
pub struct TickData {
@@ -146,7 +145,6 @@ impl Level {
root_folder: PathBuf,
seed: i64,
dimension: Dimension,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Self> {
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
Some((ns, n)) => (ns, n),
@@ -281,19 +279,10 @@ impl Level {
level_channel: level_channel.clone(),
thread_tracker,
chunk_listener: listener.clone(),
gen_pool: gen_pool.clone(),
});
// TODO
let total_cores = thread::available_parallelism()
.map_or(1, std::num::NonZero::get)
.saturating_sub(2)
.max(1);
let threads_per_dimension = (total_cores / 2).max(1);
GenerationSchedule::create(
4,
threads_per_dimension,
level_ref.clone(),
level_channel,
listener,
@@ -302,7 +291,6 @@ impl Level {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_mut(),
gen_pool,
);
level_ref
@@ -319,48 +307,22 @@ impl Level {
pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) {
let level = self.clone();
if let Some(pool) = &self.gen_pool {
pool.spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: tokio::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
});
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
rayon::spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: std::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
});
} else {
// Fallback to spawning a new thread if no pool is available (should not happen in production)
let level_clone = level;
let _ = thread::Builder::new()
.name(format!("Entity Gen {pos:?}"))
.spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: tokio::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
});
level_clone
.loaded_entity_chunks
.insert(pos, arc_chunk.clone());
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
if let Some((_, waiters)) = level_clone.pending_entity_generations.remove(&pos)
{
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
});
}
if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
for tx in waiters {
let _ = tx.send(arc_chunk.clone());
}
}
});
}
/// Spawns a task associated with this world. All tasks spawned with this method are awaited
@@ -750,20 +712,18 @@ impl Level {
let _ = sender.send((Arc::downgrade(&chunk), true)).await;
}
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
let sender_clone = sender.clone();
let level_clone = level.clone();
tokio::spawn(async move {
let (tx, rx) = oneshot::channel();
match level_clone.pending_entity_generations.entry(pos) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
entry.get_mut().push(tx);
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
level_clone.spawn_entity_generation(pos);
}
let (tx, rx) = oneshot::channel();
match level.pending_entity_generations.entry(pos) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
entry.get_mut().push(tx);
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(vec![tx]);
level.spawn_entity_generation(pos);
}
}
let sender_clone = sender.clone();
tokio::spawn(async move {
if let Ok(chunk) = rx.await {
let _ =
sender_clone.send((Arc::downgrade(&chunk), true)).await;
@@ -807,7 +767,7 @@ impl Level {
Arc::new(ChunkEntityData {
x: pos.x,
z: pos.y,
data: tokio::sync::Mutex::new(Vec::new()),
data: std::sync::Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
})
})
@@ -1025,7 +985,7 @@ mod tests {
let config = LevelConfig::default();
let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None);
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD);
assert_eq!(
overworld_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("overworld")
@@ -1038,14 +998,13 @@ mod tests {
.join("region")
);
let nether_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
assert_eq!(
nether_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_nether")
);
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None);
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END);
assert_eq!(
end_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_end")
@@ -1064,14 +1023,13 @@ mod tests {
std::fs::create_dir_all(root.join("DIM1").join("region")).unwrap();
let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None);
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD);
assert_eq!(overworld_level.level_folder.dim_folder, root);
let nether_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
assert_eq!(nether_level.level_folder.dim_folder, root.join("DIM-1"));
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None);
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END);
assert_eq!(end_level.level_folder.dim_folder, root.join("DIM1"));
}
}

View File

@@ -1,4 +1,3 @@
use std::pin::Pin;
use std::{any::Any, sync::Arc};
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
@@ -126,19 +125,10 @@ pub trait BlockEntity: Any + Send + Sync {
None
}
fn set_block_state(&mut self, _block_state: BlockStateId) {}
fn on_block_replaced<'a>(
self: Arc<Self>,
world: Arc<World>,
position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
if let Some(inventory) = self.get_inventory() {
world.scatter_inventory(&position, &inventory);
}
})
fn on_block_replaced(self: Arc<Self>, world: &Arc<World>, position: &BlockPos) {
if let Some(inventory) = self.get_inventory() {
world.scatter_inventory(position, &inventory);
}
}
fn is_dirty(&self) -> bool {
false

View File

@@ -3,8 +3,6 @@ use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{array::from_fn, sync::Arc};
@@ -63,17 +61,8 @@ impl BlockEntity for ShulkerBoxBlockEntity {
.update_viewer_count::<Self>(self, world, &self.position);
}
fn on_block_replaced<'a>(
self: Arc<Self>,
_world: Arc<World>,
_position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
// Do nothing
})
fn on_block_replaced(self: Arc<Self>, _world: &Arc<World>, _position: &BlockPos) {
// Shulker boxes retain items when broken
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {

View File

@@ -50,84 +50,16 @@ static ERROR_CREATE_IO_FAILURE: CommandErrorType<1> = CommandErrorType::new(
translation::java::COMMANDS_DATAPACK_CREATE_IO_FAILURE,
);
fn get_all_known_packs(server: &Server) -> Vec<String> {
let mut packs = Vec::new();
packs.push("vanilla".to_string());
// Bundled feature packs
for bundled in [
"trade_rebalance",
"minecart_improvements",
"redstone_experiments",
] {
if !packs.iter().any(|p| p == bundled) {
packs.push(bundled.to_string());
}
}
// World datapacks directory
let datapacks_dir = server.basic_config.get_world_path().join("datapacks");
if let Ok(entries) = fs::read_dir(datapacks_dir) {
for entry in entries.flatten() {
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.starts_with('.') {
continue;
}
if path.is_dir()
|| path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
let pack_name = format!("file/{file_name}");
if !packs.iter().any(|p| p == &pack_name) {
packs.push(pack_name);
}
}
}
}
let level_info = server.level_info.load();
for pack in &level_info.data_packs.enabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
for pack in &level_info.data_packs.disabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
packs
}
fn get_enabled_packs(server: &Server) -> Vec<String> {
server.level_info.load().data_packs.enabled.clone()
crate::data::datapack::DatapackManager::get_enabled_packs(server)
}
fn get_available_packs(server: &Server) -> Vec<String> {
let enabled = get_enabled_packs(server);
let all = get_all_known_packs(server);
all.into_iter().filter(|p| !enabled.contains(p)).collect()
crate::data::datapack::DatapackManager::get_available_packs(server)
}
fn find_pack_name(server: &Server, input: &str) -> Option<String> {
let known = get_all_known_packs(server);
if let Some(p) = known.iter().find(|p| *p == input) {
return Some(p.clone());
}
let file_input = format!("file/{input}");
if let Some(p) = known.iter().find(|p| **p == file_input) {
return Some(p.clone());
}
if let Some(p) = known
.iter()
.find(|p| p.strip_prefix("file/") == Some(input))
{
return Some(p.clone());
}
None
crate::data::datapack::DatapackManager::find_pack_name(server, input)
}
fn format_pack(name: &str) -> TextComponent {

View File

@@ -267,28 +267,32 @@ struct ResolveNameExecutor;
impl CommandExecutor for ResolveNameExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let name = StringArgumentType::get(context, ARG_NAME)?;
let server = context.server();
let server = context.server().clone();
let source = context.source.clone();
let name_owned = name.to_string();
let name_component = TextComponent::text(name_owned.clone());
let result = futures::executor::block_on(fetch_profile_by_name_helper(server, &name_owned));
match result {
Some(profile) => {
report_resolved_profile(
&context.source,
&profile,
translation::java::COMMANDS_FETCHPROFILE_NAME_SUCCESS,
name_component,
);
let runtime = server.runtime.clone();
runtime.spawn(async move {
let result = fetch_profile_by_name_helper(&server, &name_owned).await;
match result {
Some(profile) => {
report_resolved_profile(
&source,
&profile,
translation::java::COMMANDS_FETCHPROFILE_NAME_SUCCESS,
name_component,
);
}
None => {
source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
[name_component],
));
}
}
None => {
context.source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_NAME_FAILURE,
[name_component],
));
}
}
});
Ok(1)
}
@@ -299,27 +303,31 @@ struct ResolveIdExecutor;
impl CommandExecutor for ResolveIdExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let id = UuidArgumentType::get(context, ARG_ID)?;
let server = context.server();
let server = context.server().clone();
let source = context.source.clone();
let id_component = TextComponent::text(id.to_string());
let result = futures::executor::block_on(fetch_profile_by_id_helper(server, id));
match result {
Some(profile) => {
report_resolved_profile(
&context.source,
&profile,
translation::java::COMMANDS_FETCHPROFILE_ID_SUCCESS,
id_component,
);
let runtime = server.runtime.clone();
runtime.spawn(async move {
let result = fetch_profile_by_id_helper(&server, id).await;
match result {
Some(profile) => {
report_resolved_profile(
&source,
&profile,
translation::java::COMMANDS_FETCHPROFILE_ID_SUCCESS,
id_component,
);
}
None => {
source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
[id_component],
));
}
}
None => {
context.source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
translation::java::COMMANDS_FETCHPROFILE_ID_FAILURE,
[id_component],
));
}
}
});
Ok(1)
}

View File

@@ -30,7 +30,6 @@ struct FillBiomeExecutor {
}
impl CommandExecutor for FillBiomeExecutor {
#[expect(clippy::too_many_lines)]
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?;
let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?;
@@ -96,51 +95,30 @@ impl CommandExecutor for FillBiomeExecutor {
let mut changed_count = 0;
for (chunk_pos, mods) in chunk_modifications {
let (has_replaced, count) =
futures::executor::block_on(world.level.get_or_fetch_chunk(chunk_pos, |chunk| {
let mut local_count = 0;
let mut modified = false;
for &(rel_x, rel_y, rel_z) in &mods {
let section_index = rel_y / 4;
let scale_y = rel_y % 4;
if let Some(current_id) =
chunk
.section
.get_noise_biome(section_index, rel_x, scale_y, rel_z)
{
if let Some(replace_id) = replace_biome_id {
if current_id == replace_id {
chunk.section.set_relative_biome(
rel_x,
rel_y,
rel_z,
target_biome_id,
);
local_count += 1;
modified = true;
}
} else {
chunk.section.set_relative_biome(
rel_x,
rel_y,
rel_z,
target_biome_id,
);
local_count += 1;
modified = true;
}
}
let result = world.level.read_chunk_sync(&chunk_pos, |chunk| {
let mut local_count = 0;
let mut modified = false;
for &(rel_x, rel_y, rel_z) in &mods {
let section_index = rel_y / 4;
let scale_y = rel_y % 4;
if let Some(current_id) =
chunk
.section
.get_noise_biome(section_index, rel_x, scale_y, rel_z)
&& replace_biome_id.is_none_or(|rep| current_id == rep)
{
chunk
.section
.set_relative_biome(rel_x, rel_y, rel_z, target_biome_id);
local_count += 1;
modified = true;
}
(modified, local_count)
}));
}
(local_count, modified.then(|| chunk.clone()))
});
if has_replaced {
if let Some((count, Some(chunk))) = result {
changed_count += count;
let chunk = futures::executor::block_on(
world
.level
.get_or_fetch_chunk(chunk_pos, std::clone::Clone::clone),
);
world.broadcast_to_chunk_except(chunk_pos, &[], &CChunkData(&chunk));
}
}

View File

@@ -34,9 +34,8 @@ impl CommandExecutor for ListExecutor {
)));
};
let plugins = futures::executor::block_on(server_arc.plugin_manager.active_plugins());
let loaded_plugins =
futures::executor::block_on(server_arc.plugin_manager.loaded_plugins());
let plugins = server_arc.plugin_manager.active_plugins();
let loaded_plugins = server_arc.plugin_manager.loaded_plugins();
let mut message = TextComponent::text(format!("Plugins ({}):", loaded_plugins.len()))
.color_named(NamedColor::Gold)
@@ -59,11 +58,18 @@ impl CommandExecutor for ListExecutor {
metadata.authors.join(", "),
metadata.description
);
let component = TextComponent::text(line)
let mut plugin_component = TextComponent::text(line)
.color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
message = message.add_child(component);
if !metadata.permissions.is_empty() {
plugin_component = plugin_component.add_child(
TextComponent::text(format!(" (Permissions: {:?})", metadata.permissions))
.color_named(NamedColor::Gray),
);
}
message = message.add_child(plugin_component);
}
sender.send_message(message);
@@ -95,32 +101,38 @@ impl CommandExecutor for LoadExecutor {
};
let plugin_name = plugin_name.to_string();
if futures::executor::block_on(server_arc.plugin_manager.is_plugin_active(&plugin_name)) {
if server_arc.plugin_manager.is_plugin_active(&plugin_name) {
sender.send_message(TextComponent::text(format!(
"Plugin {plugin_name} is already loaded"
)));
return Ok(1);
}
let result = futures::executor::block_on(
server_arc
let sender_clone = sender.clone();
let plugin_name_clone = plugin_name;
let server_clone = server_arc.clone();
server_arc.runtime.spawn(async move {
let result = server_clone
.plugin_manager
.try_load_plugin(&server_arc, Path::new(&plugin_name)),
);
.try_load_plugin(&server_clone, Path::new(&plugin_name_clone))
.await;
match result {
Ok(()) => {
sender.send_message(
TextComponent::text(format!("Plugin {plugin_name} loaded successfully"))
match result {
Ok(()) => {
sender_clone.send_message(
TextComponent::text(format!(
"Plugin {plugin_name_clone} loaded successfully"
))
.color_named(NamedColor::Green),
);
);
}
Err(e) => {
sender_clone.send_message(TextComponent::text(format!(
"Failed to load plugin {plugin_name_clone}: {e}"
)));
}
}
Err(e) => {
sender.send_message(TextComponent::text(format!(
"Failed to load plugin {plugin_name}: {e}"
)));
}
}
});
Ok(1)
}
@@ -149,29 +161,38 @@ impl CommandExecutor for UnloadExecutor {
};
let plugin_name = plugin_name.to_string();
if !futures::executor::block_on(server_arc.plugin_manager.is_plugin_active(&plugin_name)) {
if !server_arc.plugin_manager.is_plugin_active(&plugin_name) {
sender.send_message(TextComponent::text(format!(
"Plugin {plugin_name} is not loaded"
)));
return Ok(1);
}
let result =
futures::executor::block_on(server_arc.plugin_manager.unload_plugin(&plugin_name));
let sender_clone = sender.clone();
let plugin_name_clone = plugin_name;
let server_clone = server_arc.clone();
server_arc.runtime.spawn(async move {
let result = server_clone
.plugin_manager
.unload_plugin(&plugin_name_clone)
.await;
match result {
Ok(()) => {
sender.send_message(
TextComponent::text(format!("Plugin {plugin_name} unloaded successfully"))
match result {
Ok(()) => {
sender_clone.send_message(
TextComponent::text(format!(
"Plugin {plugin_name_clone} unloaded successfully"
))
.color_named(NamedColor::Green),
);
);
}
Err(e) => {
sender_clone.send_message(TextComponent::text(format!(
"Failed to unload plugin {plugin_name_clone}: {e}"
)));
}
}
Err(e) => {
sender.send_message(TextComponent::text(format!(
"Failed to unload plugin {plugin_name}: {e}"
)));
}
}
});
Ok(1)
}
@@ -197,32 +218,36 @@ impl CommandExecutor for HotReloadExecutor {
)));
};
let sender_clone = sender.clone();
let server_clone = server_arc.clone();
if enabled {
if let Err(e) =
futures::executor::block_on(server_arc.plugin_manager.start_watcher(&server_arc))
{
sender.send_message(TextComponent::text(format!(
"Failed to start plugin watcher: {e}"
)));
return Ok(1);
}
server_arc.runtime.spawn(async move {
if let Err(e) = server_clone.plugin_manager.start_watcher(&server_clone).await {
sender_clone.send_message(TextComponent::text(format!(
"Failed to start plugin watcher: {e}"
)));
return;
}
sender.send_message(
TextComponent::text("Hot reloading has been enabled.")
.color_named(NamedColor::Green),
);
sender.send_message(
TextComponent::text(
"WARNING: Hot reloading can impact performance and should only be enabled during plugin development.",
)
.color_named(NamedColor::Red),
);
sender_clone.send_message(
TextComponent::text("Hot reloading has been enabled.")
.color_named(NamedColor::Green),
);
sender_clone.send_message(
TextComponent::text(
"WARNING: Hot reloading can impact performance and should only be enabled during plugin development.",
)
.color_named(NamedColor::Red),
);
});
} else {
futures::executor::block_on(server_arc.plugin_manager.stop_watcher());
sender.send_message(
TextComponent::text("Hot reloading has been disabled.")
.color_named(NamedColor::Yellow),
);
server_arc.runtime.spawn(async move {
server_clone.plugin_manager.stop_watcher().await;
sender_clone.send_message(
TextComponent::text("Hot reloading has been disabled.")
.color_named(NamedColor::Yellow),
);
});
}
Ok(1)

View File

@@ -28,7 +28,7 @@ impl CommandExecutor for PluginsExecutor {
)));
};
let plugins = futures::executor::block_on(server_arc.plugin_manager.active_plugins());
let plugins = server_arc.plugin_manager.active_plugins();
let message_text = if plugins.is_empty() {
TextComponent::text("No plugins are loaded on the server.").color_named(NamedColor::Red)

View File

@@ -108,11 +108,9 @@ impl CommandExecutor for RideMountExecutor {
continue;
}
// Dismount first
futures::executor::block_on(
curr_veh
.get_entity()
.remove_passenger(target.get_entity().entity_id),
);
curr_veh
.get_entity()
.remove_passenger_sync(target.get_entity().entity_id);
}
vehicle
@@ -160,7 +158,7 @@ impl CommandExecutor for RideDismountExecutor {
.clone();
if let Some(vehicle) = current_vehicle {
let target_id = target.get_entity().entity_id;
futures::executor::block_on(vehicle.get_entity().remove_passenger(target_id));
vehicle.get_entity().remove_passenger_sync(target_id);
success_count += 1;
let msg = TextComponent::translate_cross(

View File

@@ -53,7 +53,7 @@ fn get_anchor_position(entity: &crate::entity::Entity, anchor: EntityAnchor) ->
/// If relative flags are true, the values are added to current rotation.
/// If relative flags are false, the values are absolute.
fn rotate_entity(
target: std::sync::Arc<dyn crate::entity::EntityBase>,
target: &std::sync::Arc<dyn crate::entity::EntityBase>,
yaw: f32,
is_yaw_relative: bool,
pitch: f32,
@@ -80,7 +80,14 @@ fn rotate_entity(
// This properly handles both players (sends CPlayerPosition) and other entities
let pos = entity.pos.load();
let world = entity.world.load_full();
futures::executor::block_on(target.teleport(pos, Some(final_yaw), Some(final_pitch), world));
if let Some(player) = target.get_player() {
let fut = target
.clone()
.teleport(pos, Some(final_yaw), Some(final_pitch), world);
player.spawn_task(fut);
} else {
entity.teleport(pos, Some(final_yaw), Some(final_pitch), world);
}
}
/// Sends success message for the rotate command.
@@ -107,7 +114,7 @@ impl CommandExecutor for RotateToRotationExecutor {
let (yaw, yaw_rel, pitch, pitch_rel) =
RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?;
rotate_entity(target.clone(), yaw, yaw_rel, pitch, pitch_rel);
rotate_entity(&target, yaw, yaw_rel, pitch, pitch_rel);
send_success_message(sender, target.as_ref());
Ok(1)
@@ -135,7 +142,7 @@ impl CommandExecutor for RotateFacingLocationExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
// Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false);
rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref());
Ok(1)
@@ -165,7 +172,7 @@ impl CommandExecutor for RotateFacingEntityExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
// Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false);
rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref());
Ok(1)
@@ -196,7 +203,7 @@ impl CommandExecutor for RotateFacingEntityNoAnchorExecutor {
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
// Facing uses absolute rotation
rotate_entity(target.clone(), yaw, false, pitch, false);
rotate_entity(&target, yaw, false, pitch, false);
send_success_message(sender, target.as_ref());
Ok(1)

View File

@@ -26,24 +26,28 @@ impl CommandExecutor for SaveAllExecutor {
false,
);
let server = context.server();
if let Err(err) = futures::executor::block_on(server.save_all()) {
error!("Failed to save server data: {err}");
context.source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_FAILED,
translation::bedrock::COMMANDS_SAVE_FAILED,
[],
));
} else {
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_SUCCESS,
translation::bedrock::COMMANDS_SAVE_SUCCESS,
let server_arc = context.server().clone();
let source = context.source.clone();
let runtime = server_arc.runtime.clone();
runtime.spawn(async move {
if let Err(err) = server_arc.save_all().await {
error!("Failed to save server data: {err}");
source.send_error(TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_FAILED,
translation::bedrock::COMMANDS_SAVE_FAILED,
[],
),
true,
);
}
));
} else {
source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_SAVE_SUCCESS,
translation::bedrock::COMMANDS_SAVE_SUCCESS,
[],
),
true,
);
}
});
Ok(1)
}

View File

@@ -105,7 +105,11 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
let yaw = target_entity.yaw.load();
let pitch = target_entity.pitch.load();
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
futures::executor::block_on(player.teleport(pos, Some(yaw), Some(pitch), player_world));
player.spawn_task(
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player_world),
);
let target_name = target.get_display_name();
sender.send_message(TextComponent::translate_cross(
@@ -174,12 +178,11 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
let pitch = target_entity.pitch.load();
let player_world = player.world();
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
futures::executor::block_on(player.clone().teleport(
pos,
Some(yaw),
Some(pitch),
player_world,
));
player.spawn_task(
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player_world),
);
succeeded += 1;
}

View File

@@ -269,7 +269,7 @@ impl CommandExecutor for SpreadPlayersExecutor {
for (index, target) in targets.iter().enumerate() {
let pile = piles[index % pile_count];
let y = surface_ys[index % pile_count];
futures::executor::block_on(target.clone().teleport(
context.server().runtime.spawn(target.clone().teleport(
Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5),
None,
None,

View File

@@ -82,7 +82,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
fn execute(
&self,
sender: &CommandSender,
_server: &crate::server::Server,
server: &crate::server::Server,
args: &ConsumedArgs,
) -> CommandResult {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
@@ -101,7 +101,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
)));
}
for target in targets {
futures::executor::block_on(target.clone().teleport(
server.runtime.spawn(target.clone().teleport(
pos,
Some(yaw),
Some(pitch),
@@ -148,7 +148,7 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
let world = resolve_sender_world(sender, server)?;
for target in targets {
futures::executor::block_on(target.clone().teleport(
server.runtime.spawn(target.clone().teleport(
pos,
Some(yaw),
Some(pitch),
@@ -200,7 +200,7 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
let world = resolve_sender_world(sender, server)?;
for target in targets {
futures::executor::block_on(target.clone().teleport(
server.runtime.spawn(target.clone().teleport(
pos,
Some(yaw),
Some(pitch),
@@ -253,7 +253,7 @@ impl CommandExecutor for EntitiesToPosWithRotationExecutor {
let world = resolve_sender_world(sender, server)?;
for target in targets {
futures::executor::block_on(target.clone().teleport(
server.runtime.spawn(target.clone().teleport(
pos,
Some(yaw),
Some(pitch),
@@ -304,7 +304,7 @@ impl CommandExecutor for EntitiesToPosExecutor {
for target in targets {
let yaw = target.get_entity().yaw.load();
let pitch = target.get_entity().pitch.load();
futures::executor::block_on(target.clone().teleport(
server.runtime.spawn(target.clone().teleport(
pos,
Some(yaw),
Some(pitch),
@@ -338,7 +338,7 @@ impl CommandExecutor for SelfToEntityExecutor {
fn execute(
&self,
sender: &CommandSender,
_server: &crate::server::Server,
server: &crate::server::Server,
args: &ConsumedArgs,
) -> CommandResult {
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
@@ -357,12 +357,9 @@ impl CommandExecutor for SelfToEntityExecutor {
[],
)));
}
futures::executor::block_on(player.clone().teleport(
pos,
Some(yaw),
Some(pitch),
world,
));
server
.runtime
.spawn(player.clone().teleport(pos, Some(yaw), Some(pitch), world));
sender.send_message(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
@@ -390,7 +387,7 @@ impl CommandExecutor for SelfToPosExecutor {
fn execute(
&self,
sender: &CommandSender,
_server: &crate::server::Server,
server: &crate::server::Server,
args: &ConsumedArgs,
) -> CommandResult {
match sender {
@@ -406,7 +403,7 @@ impl CommandExecutor for SelfToPosExecutor {
)));
}
let player_world = player.world();
futures::executor::block_on(player.clone().teleport(
server.runtime.spawn(player.clone().teleport(
pos,
Some(yaw),
Some(pitch),

View File

@@ -24,6 +24,25 @@ pub struct LoadedDatapack {
pub function_count: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DatapackInfo {
pub id: String,
pub name: String,
pub description: String,
pub pack_format: u32,
pub is_enabled: bool,
pub recipe_count: usize,
pub function_count: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DatapackEnablePosition {
First,
Last,
Before(String),
After(String),
}
pub struct DatapackManager {
loaded_packs: RwLock<Vec<LoadedDatapack>>,
functions: RwLock<HashMap<String, Vec<String>>>,
@@ -244,9 +263,314 @@ impl DatapackManager {
Ok(total_executed)
}
pub fn get_all_known_packs(server: &Server) -> Vec<String> {
let mut packs = Vec::new();
packs.push("vanilla".to_string());
// Bundled feature packs
for bundled in [
"trade_rebalance",
"minecart_improvements",
"redstone_experiments",
] {
if !packs.iter().any(|p| p == bundled) {
packs.push(bundled.to_string());
}
}
// World datapacks directory
let datapacks_dir = server.basic_config.get_world_path().join("datapacks");
if let Ok(entries) = fs::read_dir(datapacks_dir) {
for entry in entries.flatten() {
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.starts_with('.') {
continue;
}
if path.is_dir()
|| path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
let pack_name = format!("file/{file_name}");
if !packs.iter().any(|p| p == &pack_name) {
packs.push(pack_name);
}
}
}
}
let level_info = server.level_info.load();
for pack in &level_info.data_packs.enabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
for pack in &level_info.data_packs.disabled {
if !packs.iter().any(|p| p == pack) {
packs.push(pack.clone());
}
}
packs
}
pub fn get_enabled_packs(server: &Server) -> Vec<String> {
server.level_info.load().data_packs.enabled.clone()
}
pub fn get_available_packs(server: &Server) -> Vec<String> {
let enabled = Self::get_enabled_packs(server);
let all = Self::get_all_known_packs(server);
all.into_iter().filter(|p| !enabled.contains(p)).collect()
}
pub fn find_pack_name(server: &Server, input: &str) -> Option<String> {
let known = Self::get_all_known_packs(server);
if let Some(p) = known.iter().find(|p| *p == input) {
return Some(p.clone());
}
let file_input = format!("file/{input}");
if let Some(p) = known.iter().find(|p| **p == file_input) {
return Some(p.clone());
}
if let Some(p) = known
.iter()
.find(|p| p.strip_prefix("file/") == Some(input))
{
return Some(p.clone());
}
None
}
pub fn get_pack_info(server: &Server, name_or_id: &str) -> Option<DatapackInfo> {
let resolved_name = Self::find_pack_name(server, name_or_id)?;
let enabled_packs = Self::get_enabled_packs(server);
let is_enabled = enabled_packs.contains(&resolved_name);
let loaded = server.datapack_manager.get_loaded_packs();
if let Some(pack) = loaded
.iter()
.find(|p| p.id == resolved_name || p.name == resolved_name)
{
return Some(DatapackInfo {
id: pack.id.clone(),
name: pack.name.clone(),
description: pack.description.clone(),
pack_format: pack.pack_format,
is_enabled,
recipe_count: pack.recipe_count,
function_count: pack.function_count,
});
}
let (id, name, description, pack_format) = if resolved_name == "vanilla" {
(
"vanilla".to_string(),
"vanilla".to_string(),
"The default data pack".to_string(),
61,
)
} else if let Some(stripped) = resolved_name.strip_prefix("file/") {
let pack_path = server
.basic_config
.get_world_path()
.join("datapacks")
.join(stripped);
let (desc, format) = read_pack_mcmeta(&pack_path);
(resolved_name.clone(), stripped.to_string(), desc, format)
} else {
(
resolved_name.clone(),
resolved_name.clone(),
format!("Bundled datapack: {resolved_name}"),
61,
)
};
Some(DatapackInfo {
id,
name,
description,
pack_format,
is_enabled,
recipe_count: 0,
function_count: 0,
})
}
pub fn list_all_packs(server: &Server) -> Vec<DatapackInfo> {
let all = Self::get_all_known_packs(server);
all.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn list_enabled_packs(server: &Server) -> Vec<DatapackInfo> {
let enabled = Self::get_enabled_packs(server);
enabled
.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn list_available_packs(server: &Server) -> Vec<DatapackInfo> {
let available = Self::get_available_packs(server);
available
.into_iter()
.filter_map(|p| Self::get_pack_info(server, &p))
.collect()
}
pub fn is_pack_enabled(server: &Server, name: &str) -> bool {
let Some(resolved) = Self::find_pack_name(server, name) else {
return false;
};
Self::get_enabled_packs(server).contains(&resolved)
}
pub fn enable_pack(
server: &Arc<Server>,
name: &str,
position: DatapackEnablePosition,
) -> Result<(), String> {
let Some(resolved_name) = Self::find_pack_name(server, name) else {
return Err(format!("Unknown datapack '{name}'"));
};
let enabled = Self::get_enabled_packs(server);
if enabled.contains(&resolved_name) {
return Err(format!("Datapack '{resolved_name}' is already enabled"));
}
let target = resolved_name;
match position {
DatapackEnablePosition::First => {
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
new_info.data_packs.enabled.insert(0, target.clone());
new_info
});
}
DatapackEnablePosition::Last => {
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
new_info.data_packs.enabled.push(target.clone());
new_info
});
}
DatapackEnablePosition::Before(existing_name) => {
let Some(existing_pack) = Self::find_pack_name(server, &existing_name) else {
return Err(format!("Unknown existing datapack '{existing_name}'"));
};
if !enabled.contains(&existing_pack) {
return Err(format!("Datapack '{existing_pack}' is not enabled"));
}
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
if let Some(idx) = new_info
.data_packs
.enabled
.iter()
.position(|p| p == &existing_pack)
{
new_info.data_packs.enabled.insert(idx, target.clone());
} else {
new_info.data_packs.enabled.push(target.clone());
}
new_info
});
}
DatapackEnablePosition::After(existing_name) => {
let Some(existing_pack) = Self::find_pack_name(server, &existing_name) else {
return Err(format!("Unknown existing datapack '{existing_name}'"));
};
if !enabled.contains(&existing_pack) {
return Err(format!("Datapack '{existing_pack}' is not enabled"));
}
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.disabled.retain(|p| p != &target);
new_info.data_packs.enabled.retain(|p| p != &target);
if let Some(idx) = new_info
.data_packs
.enabled
.iter()
.position(|p| p == &existing_pack)
{
new_info.data_packs.enabled.insert(idx + 1, target.clone());
} else {
new_info.data_packs.enabled.push(target.clone());
}
new_info
});
}
}
if let Err(err) = server.save_world_info() {
tracing::error!("Failed to save world info: {err}");
}
server.reload_datapacks(server);
Ok(())
}
pub fn disable_pack(server: &Arc<Server>, name: &str) -> Result<(), String> {
let Some(target_pack) = Self::find_pack_name(server, name) else {
return Err(format!("Unknown datapack '{name}'"));
};
let enabled = Self::get_enabled_packs(server);
if !enabled.contains(&target_pack) {
return Err(format!("Datapack '{target_pack}' is not enabled"));
}
if target_pack == "vanilla" {
return Err("Cannot disable the default vanilla datapack".to_string());
}
let target = target_pack;
server.level_info.rcu(|level_info| {
let mut new_info = (**level_info).clone();
new_info.data_packs.enabled.retain(|p| p != &target);
if !new_info.data_packs.disabled.contains(&target) {
new_info.data_packs.disabled.push(target.clone());
}
new_info
});
if let Err(err) = server.save_world_info() {
tracing::error!("Failed to save world info: {err}");
}
server.reload_datapacks(server);
Ok(())
}
pub fn reload(server: &Arc<Server>) -> Result<(), String> {
server.reload_datapacks(server);
Ok(())
}
pub fn execute_function_from_console(
server: &Arc<Server>,
name: &str,
) -> Result<usize, String> {
let source = crate::command::CommandSender::Console.into_source(server);
server
.datapack_manager
.execute_function(server, &source, name)
}
}
fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) {
pub fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) {
let mcmeta_path = pack_path.join("pack.mcmeta");
if let Ok(content) = fs::read_to_string(mcmeta_path)
&& let Ok(val) = serde_json::from_str::<serde_json::Value>(&content)

View File

@@ -94,7 +94,7 @@ impl ServerPlayerData {
}
let storage = self.storage.clone();
server.runtime.spawn(async move {
tokio::task::spawn_blocking(move || {
for (uuid, nbt) in snapshots {
if let Err(e) = storage.save_player_data(&uuid, nbt) {
error!("Failed to save player data for {uuid}: {e}");

View File

@@ -247,3 +247,53 @@ impl Mob for CreeperEntity {
true
}
}
impl CreeperEntity {
pub fn is_charged(&self) -> bool {
self.charged.load(Ordering::Relaxed)
}
pub fn set_charged(&self, charged: bool) {
self.charged.store(charged, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::CHARGED,
charged,
)],
None,
);
}
pub fn is_ignited(&self) -> bool {
self.ignited.load(Ordering::Relaxed)
}
pub fn set_ignited(&self, ignited: bool) {
self.ignited.store(ignited, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::IS_IGNITED,
ignited,
)],
None,
);
}
pub fn get_fuse(&self) -> i32 {
self.fuse_time.load(Ordering::Relaxed)
}
pub fn set_fuse(&self, fuse: i32) {
self.fuse_time.store(fuse, Ordering::Relaxed);
}
pub fn get_explosion_radius(&self) -> i32 {
self.explosion_radius.load(Ordering::Relaxed)
}
pub fn set_explosion_radius(&self, radius: i32) {
self.explosion_radius.store(radius, Ordering::Relaxed);
}
}

View File

@@ -535,8 +535,9 @@ impl Goal for EvokerWololoSpellGoal {
for cand in candidates {
if *cand.get_entity().entity_type == EntityType::SHEEP
&& let Some(mob) = cand.get_mob()
&& let Some(sheep) = mob.get_sheep()
&& let Some(sheep) = cand
.cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{
// Blue color is 11 in Minecraft
if sheep.get_color() == 11 {
@@ -609,8 +610,9 @@ impl Goal for EvokerWololoSpellGoal {
for cand in candidates {
if cand.get_entity().entity_id == target_id
&& let Some(mob) = cand.get_mob()
&& let Some(sheep) = mob.get_sheep()
&& let Some(sheep) = cand
.cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{
// Convert color to Red (14)
sheep.set_color(14);

View File

@@ -957,10 +957,6 @@ pub trait Mob: EntityBase + Send + Sync {
fn mob_set_variant_name(&self, _name: &str) {}
fn get_sheep(&self) -> Option<&crate::entity::passive::sheep::SheepEntity> {
None
}
fn mob_on_lightning_strike(
&self,
caller: &dyn EntityBase,

View File

@@ -113,7 +113,7 @@ impl ShulkerEntity {
.unwrap_or(DEFAULT_ATTACH_FACE)
}
fn set_attach_face(&self, face: BlockDirection) {
pub fn set_attach_face(&self, face: BlockDirection) {
self.attach_face.store(face as u8, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
@@ -125,6 +125,24 @@ impl ShulkerEntity {
);
}
pub fn get_color(&self) -> Option<u8> {
let c = self.color.load(Ordering::Relaxed);
if c == NO_COLOR { None } else { Some(c) }
}
pub fn set_color(&self, color: Option<u8>) {
let val = color.unwrap_or(NO_COLOR);
self.color.store(val, Ordering::Relaxed);
let entity = &self.mob_entity.living_entity.entity;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::shulker::COLOR,
val as i8,
)],
None,
);
}
pub fn get_raw_peek(&self) -> u8 {
self.peek_amount.load(Ordering::Relaxed)
}

View File

@@ -52,3 +52,46 @@ impl Mob for ZombieEntity {
self.entity.mob_read_nbt(nbt);
}
}
impl ZombieEntity {
#[must_use]
pub fn can_break_doors(&self) -> bool {
self.entity
.can_break_doors
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_can_break_doors(&self, can_break: bool) {
self.entity
.can_break_doors
.store(can_break, std::sync::atomic::Ordering::Relaxed);
}
#[must_use]
pub fn is_baby(&self) -> bool {
self.entity
.mob_entity
.living_entity
.entity
.age
.load(std::sync::atomic::Ordering::Relaxed)
< 0
}
pub fn set_baby(&self, baby: bool) {
let age = if baby { -24000 } else { 0 };
self.entity
.mob_entity
.living_entity
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
self.entity.mob_entity.living_entity.entity.send_meta_data(
&[pumpkin_protocol::java::client::play::Metadata::new(
pumpkin_data::tracked_data::zombie::BABY,
baby,
)],
None,
);
}
}

View File

@@ -2351,29 +2351,18 @@ impl Entity {
tokio::spawn(async move {
let world_for_dest = world_clone.clone();
let caller_for_dest = caller_clone.clone();
let gen_pool = world_for_dest.level.gen_pool.clone();
let transition = if let Some(pool) = gen_pool {
let (tx, rx) = tokio::sync::oneshot::channel();
pool.spawn(move || {
let dest = portal_type.get_portal_destination(
&world_for_dest,
dest_world_opt,
&caller_for_dest,
entry_pos,
src_portal.as_ref(),
);
let _ = tx.send(dest);
});
rx.await.ok().flatten()
} else {
portal_type.get_portal_destination(
let (tx, rx) = tokio::sync::oneshot::channel();
rayon::spawn(move || {
let dest = portal_type.get_portal_destination(
&world_for_dest,
dest_world_opt,
&caller_for_dest,
entry_pos,
src_portal.as_ref(),
)
};
);
let _ = tx.send(dest);
});
let transition = rx.await.ok().flatten();
if let Some(transition) = transition {
let dest_world = transition.new_world.clone();
@@ -3164,7 +3153,7 @@ impl Entity {
}
}
fn teleport(
pub fn teleport(
&self,
position: Vector3<f64>,
yaw: Option<f32>,
@@ -3485,6 +3474,10 @@ impl Entity {
);
}
pub fn remove_passenger_sync(&self, passenger_id: i32) {
self.remove_passenger_on_disconnect(passenger_id);
}
pub async fn remove_passenger(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, true).await;
}

View File

@@ -119,9 +119,6 @@ impl IronGolemEntity {
}
impl Mob for IronGolemEntity {
fn as_iron_golem(&self) -> Option<&IronGolemEntity> {
Some(self)
}
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_bool("PlayerCreated", self.is_player_created());
}

View File

@@ -156,10 +156,6 @@ impl Mob for SheepEntity {
self.set_sheared(false);
}
fn get_sheep(&self) -> Option<&SheepEntity> {
Some(self)
}
fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
use super::animal::Animal;
self.animal_interact(player, item_stack, Sound::EntitySheepAmbient)

View File

@@ -328,3 +328,21 @@ impl Mob for WolfEntity {
);
}
}
impl WolfEntity {
pub fn get_collar_color(&self) -> u8 {
self.collar_color.load(Ordering::Relaxed)
}
pub fn set_collar_color(&self, color: u8) {
self.collar_color.store(color, Ordering::Relaxed);
let entity = self.get_entity();
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::COLLAR_COLOR,
VarInt(color as i32),
)],
None,
);
}
}

View File

@@ -411,8 +411,8 @@ pub struct ChunkManager {
}
impl ChunkManager {
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 10;
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(250);
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 16;
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(100);
#[must_use]
pub fn new(

View File

@@ -76,7 +76,9 @@ impl ItemBehaviour for ShearsItem {
}
fn use_on_entity(&self, _item: &mut ItemStack, player: &Player, entity: Arc<dyn EntityBase>) {
if let Some(sheep) = entity.get_mob().and_then(|m| m.get_sheep())
if let Some(sheep) = entity
.cast_any()
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
&& !sheep.is_sheared()
{
sheep.set_sheared(true);

View File

@@ -49,6 +49,11 @@ static MAIN_THREAD: OnceLock<ThreadId> = OnceLock::new();
async fn main() {
let _ = MAIN_THREAD.set(thread::current().id());
// Initialize global Rayon thread pool with named worker threads
let _ = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("Rayon-Worker-{i}"))
.build_global();
// Set the panic handler.
std::panic::set_hook(Box::new(handle_panic));

View File

@@ -13,45 +13,60 @@ impl JavaClient {
if version.supports_configuration_state() {
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
.await;
let registry = Registry::get_synced(version);
let mut sent_dimension_type = false;
for reg in &registry {
if reg.registry_id == "minecraft:dimension_type" {
sent_dimension_type = true;
let registry_packets = tokio::task::spawn_blocking(move || {
let registry = Registry::get_synced(version);
let mut packets = Vec::new();
let mut sent_dimension_type = false;
for reg in &registry {
if reg.registry_id == "minecraft:dimension_type" {
sent_dimension_type = true;
}
let packet = CRegistryData::new(&reg.registry_id, &reg.registry_entries);
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
packets.push(data);
}
}
self.send_packet(&CRegistryData::new(&reg.registry_id, &reg.registry_entries))
.await;
}
if !sent_dimension_type {
let dims = [
&pumpkin_data::dimension::Dimension::OVERWORLD,
&pumpkin_data::dimension::Dimension::OVERWORLD_CAVES,
&pumpkin_data::dimension::Dimension::THE_END,
&pumpkin_data::dimension::Dimension::THE_NETHER,
];
let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims
.iter()
.map(|dim| pumpkin_data::registry::RegistryEntryData {
entry_id: dim.minecraft_name.to_string(),
data: Some(build_dimension_nbt(dim).into_boxed_slice()),
})
.collect();
self.send_packet(&CRegistryData::new(
&"minecraft:dimension_type".to_string(),
&dim_entries,
))
.await;
if !sent_dimension_type {
let dims = [
&pumpkin_data::dimension::Dimension::OVERWORLD,
&pumpkin_data::dimension::Dimension::OVERWORLD_CAVES,
&pumpkin_data::dimension::Dimension::THE_END,
&pumpkin_data::dimension::Dimension::THE_NETHER,
];
let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims
.iter()
.map(|dim| pumpkin_data::registry::RegistryEntryData {
entry_id: dim.minecraft_name.to_string(),
data: Some(build_dimension_nbt(dim).into_boxed_slice()),
})
.collect();
let dim_type = "minecraft:dimension_type".to_string();
let packet = CRegistryData::new(&dim_type, &dim_entries);
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
packets.push(data);
}
}
let mut tags = Vec::new();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
}
let packet = CUpdateTags::new(&tags);
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
packets.push(data);
}
packets
})
.await
.unwrap_or_default();
for packet_data in registry_packets {
self.send_packet_now(packet_data).await;
}
}
let mut tags = Vec::new();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
}
self.send_packet(&CUpdateTags::new(&tags)).await;
// We are done with configuring
self.send_packet(&CFinishConfig).await;

View File

@@ -344,51 +344,84 @@ impl JavaClient {
return;
};
if self.version.load() >= JavaMinecraftVersion::V_1_20_2 {
self.send_packet(&CChunkBatchStart).await;
}
let mut valid_chunks = Vec::with_capacity(chunks.len());
for chunk in chunks {
let mut event = ChunkSend::new(player.world(), chunk.clone());
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
continue;
if !event.cancelled {
valid_chunks.push(chunk.clone());
}
}
let mut buf = Vec::new();
let version = self.version.load();
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
error!("Failed to write chunk data id: {err:?}");
continue;
}
if let Err(err) = CChunkData(chunk).write_packet_data(&mut buf, &version) {
error!("Failed to write chunk data: {err:?}");
continue;
}
self.send_packet_now_data(buf.into()).await;
if valid_chunks.is_empty() {
return;
}
if version >= JavaMinecraftVersion::V_1_14 && version < JavaMinecraftVersion::V_1_18 {
match CLightUpdate::from_chunk(chunk, version) {
Ok(light_packet) => {
let mut light_buf = Vec::new();
if let Err(err) =
light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version)))
{
error!("Failed to write light update id: {err:?}");
} else if let Err(err) =
light_packet.write_packet_data(&mut light_buf, &version)
{
error!("Failed to write light update data: {err:?}");
} else {
self.send_packet_now_data(light_buf.into()).await;
let version = self.version.load();
// Offload CPU-heavy packet serialization (palette packing, lighting, NBT heightmaps)
// off Tokio worker threads onto blocking/Rayon worker threads
let serialize_tasks: Vec<_> = valid_chunks
.into_iter()
.map(|chunk| {
tokio::task::spawn_blocking(move || {
let mut buf = Vec::with_capacity(32 * 1024);
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
error!("Failed to write chunk data id: {err:?}");
return None;
}
if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) {
error!("Failed to write chunk data: {err:?}");
return None;
}
let light_buf = if version >= JavaMinecraftVersion::V_1_14
&& version < JavaMinecraftVersion::V_1_18
{
match CLightUpdate::from_chunk(&chunk, version) {
Ok(light_packet) => {
let mut light_buf = Vec::new();
if let Err(err) =
light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version)))
{
error!("Failed to write light update id: {err:?}");
None
} else if let Err(err) =
light_packet.write_packet_data(&mut light_buf, &version)
{
error!("Failed to write light update data: {err:?}");
None
} else {
Some(Bytes::from(light_buf))
}
}
Err(err) => {
error!("Failed to create light update packet: {err:?}");
None
}
}
}
Err(err) => {
error!("Failed to create light update packet: {err:?}");
}
} else {
None
};
Some((Bytes::from(buf), light_buf))
})
})
.collect();
if version >= JavaMinecraftVersion::V_1_20_2 {
self.send_packet(&CChunkBatchStart).await;
}
for task in serialize_tasks {
if let Ok(Some((chunk_data, light_data))) = task.await {
self.send_packet_now_data(chunk_data).await;
if let Some(light_data) = light_data {
self.send_packet_now_data(light_data).await;
}
}
}
if self.version.load() >= JavaMinecraftVersion::V_1_20_2 {
if version >= JavaMinecraftVersion::V_1_20_2 {
self.send_packet(&CChunkBatchEnd::new(chunks.len() as u16))
.await;
}

View File

@@ -145,7 +145,6 @@ async fn handle_packet(
let plugins = server
.plugin_manager
.active_plugins()
.await
.into_iter()
.map(|meta| meta.name)
.reduce(|acc, name| format!("{acc}, {name}"))

View File

@@ -316,9 +316,9 @@ impl Context {
&self,
loader: Arc<dyn crate::plugin::loader::PluginLoader>,
) -> bool {
let before_count = self.plugin_manager.loaded_plugins().await.len();
let before_count = self.plugin_manager.loaded_plugins().len();
self.plugin_manager.add_loader(&self.server, loader).await;
let after_count = self.plugin_manager.loaded_plugins().await.len();
let after_count = self.plugin_manager.loaded_plugins().len();
// Return true if any new plugins were loaded
after_count > before_count

View File

@@ -70,8 +70,22 @@ pub type EnchantmentManagerResource =
pub type OpManagerResource = WasmResource<Arc<Server>>;
pub type BanManagerResource = WasmResource<Arc<Server>>;
pub type WhitelistManagerResource = WasmResource<Arc<Server>>;
pub type DatapackManagerResource = WasmResource<Arc<Server>>;
pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>;
#[derive(Clone)]
pub enum InventoryProvider {
Generic(Arc<dyn pumpkin_world::inventory::Inventory>),
PlayerMain(Arc<Player>),
PlayerEnderChest(Arc<Player>),
}
pub type InventoryResource = WasmResource<InventoryProvider>;
pub type PlayerInventoryResource = WasmResource<Arc<Player>>;
pub type LivingEntityResource = WasmResource<Arc<dyn EntityBase>>;
pub type MobResource = WasmResource<Arc<dyn EntityBase>>;
#[derive(Clone)]
pub struct ContainerBlockEntity {
pub provider: Arc<dyn crate::block::entities::BlockEntity>,
@@ -195,6 +209,24 @@ impl PluginHostState {
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_living_entity<T>(
&mut self,
provider: Arc<dyn EntityBase>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(LivingEntityResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_mob<T>(
&mut self,
provider: Arc<dyn EntityBase>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(MobResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_world<T>(
&mut self,
provider: Arc<World>,
@@ -362,6 +394,34 @@ impl PluginHostState {
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_datapack_manager<T>(
&mut self,
provider: Arc<Server>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(DatapackManagerResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_inventory<T>(
&mut self,
provider: InventoryProvider,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(InventoryResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_player_inventory<T>(
&mut self,
provider: Arc<Player>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(PlayerInventoryResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_block_entity<T>(
&mut self,
provider: Arc<dyn crate::block::entities::BlockEntity>,

View File

@@ -319,6 +319,24 @@ impl HostContainerBlockEntity for PluginHostState {
self.add_block_entity(provider)
}
async fn get_inventory(
&mut self,
res: Resource<ContainerBlockEntity>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let container = self
.resource_table
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::Generic(inventory),
)
}
async fn get_size(&mut self, res: Resource<ContainerBlockEntity>) -> wasmtime::Result<u32> {
let container = self
.resource_table

View File

@@ -0,0 +1,154 @@
use crate::data::datapack::DatapackManager;
use crate::plugin::loader::wasm::wasm_host::state::PluginHostState;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::datapack::{
DatapackInfo as WitDatapackInfo, DatapackManager as WitDatapackManager,
EnablePosition as WitEnablePosition, Host as DatapackHost, HostDatapackManager,
};
use wasmtime::component::Resource;
impl DatapackHost for PluginHostState {}
impl HostDatapackManager for PluginHostState {
async fn list_all_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_all_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn list_enabled_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_enabled_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn list_available_packs(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Vec<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let packs = DatapackManager::list_available_packs(server);
Ok(packs.into_iter().map(to_wit_datapack_info).collect())
}
async fn get_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Option<WitDatapackInfo>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let pack = DatapackManager::get_pack_info(server, &name);
Ok(pack.map(to_wit_datapack_info))
}
async fn is_enabled(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<bool> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::is_pack_enabled(server, &name))
}
async fn enable_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
position: WitEnablePosition,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let pos = to_data_enable_position(position);
Ok(DatapackManager::enable_pack(server, &name, pos))
}
async fn disable_pack(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::disable_pack(server, &name))
}
async fn reload(
&mut self,
_res: Resource<WitDatapackManager>,
) -> wasmtime::Result<Result<(), String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
Ok(DatapackManager::reload(server))
}
async fn execute_function(
&mut self,
_res: Resource<WitDatapackManager>,
name: String,
) -> wasmtime::Result<Result<u32, String>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
let result = DatapackManager::execute_function_from_console(server, &name);
Ok(result.map(|count| count as u32))
}
async fn drop(&mut self, rep: Resource<WitDatapackManager>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::DatapackManagerResource>(
Resource::new_own(rep.rep()),
);
Ok(())
}
}
fn to_wit_datapack_info(info: crate::data::datapack::DatapackInfo) -> WitDatapackInfo {
WitDatapackInfo {
id: info.id,
name: info.name,
description: info.description,
pack_format: info.pack_format,
is_enabled: info.is_enabled,
recipe_count: info.recipe_count as u32,
function_count: info.function_count as u32,
}
}
fn to_data_enable_position(
pos: WitEnablePosition,
) -> crate::data::datapack::DatapackEnablePosition {
match pos {
WitEnablePosition::First => crate::data::datapack::DatapackEnablePosition::First,
WitEnablePosition::Last => crate::data::datapack::DatapackEnablePosition::Last,
WitEnablePosition::Before(s) => crate::data::datapack::DatapackEnablePosition::Before(s),
WitEnablePosition::After(s) => crate::data::datapack::DatapackEnablePosition::After(s),
}
}

View File

@@ -3,27 +3,18 @@ use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3;
use crate::entity::ai::goal::Goal;
use crate::entity::mob::Mob;
use crate::plugin::loader::wasm::wasm_host::{PluginInstance, WasmPlugin};
use crate::plugin::loader::wasm::wasm_host::{
state::{EntityResource, PluginHostState},
wit::v0_1::events::to_wasm_position,
wit::v0_1::pumpkin::plugin::{
attributes::{
Attribute, AttributeModifier as WitAttributeModifier,
ModifierOperation as WitModifierOperation,
},
common::{EntityPose, NbtTree as WitNbtTree, Position},
damage_types::DamageType as WitDamageType,
entity::Host,
entity_types,
item_stack::ItemStack as WitHostItemStack,
text::TextComponent,
uuid::Uuid,
world::{
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity,
EquipmentSlot as WitEquipmentSlot, HostEntity,
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
LivingEntity as WitLivingEntity, Mob as WitMob,
RayTraceBlockResult as WitRayTraceBlockResult,
RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult,
World,
@@ -37,7 +28,7 @@ use pumpkin_data::entity::EntityPose as InternalEntityPose;
impl Host for PluginHostState {}
impl entity_types::Host for PluginHostState {}
fn entity_from_resource(
pub fn entity_from_resource(
state: &PluginHostState,
entity: &Resource<Entity>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
@@ -71,112 +62,6 @@ const fn map_entity_pose(pose: InternalEntityPose) -> EntityPose {
}
}
#[must_use]
pub const fn from_wit_attribute(attr: Attribute) -> &'static pumpkin_data::attributes::Attributes {
use pumpkin_data::attributes::Attributes;
match attr {
Attribute::AirDragModifier => &Attributes::AIR_DRAG_MODIFIER,
Attribute::Armor => &Attributes::ARMOR,
Attribute::ArmorToughness => &Attributes::ARMOR_TOUGHNESS,
Attribute::AttackDamage => &Attributes::ATTACK_DAMAGE,
Attribute::AttackKnockback => &Attributes::ATTACK_KNOCKBACK,
Attribute::AttackSpeed => &Attributes::ATTACK_SPEED,
Attribute::BelowNameDistance => &Attributes::BELOW_NAME_DISTANCE,
Attribute::BlockBreakSpeed => &Attributes::BLOCK_BREAK_SPEED,
Attribute::BlockInteractionRange => &Attributes::BLOCK_INTERACTION_RANGE,
Attribute::Bounciness => &Attributes::BOUNCINESS,
Attribute::BurningTime => &Attributes::BURNING_TIME,
Attribute::CameraDistance => &Attributes::CAMERA_DISTANCE,
Attribute::ExplosionKnockbackResistance => &Attributes::EXPLOSION_KNOCKBACK_RESISTANCE,
Attribute::EntityInteractionRange => &Attributes::ENTITY_INTERACTION_RANGE,
Attribute::FallDamageMultiplier => &Attributes::FALL_DAMAGE_MULTIPLIER,
Attribute::FlyingSpeed => &Attributes::FLYING_SPEED,
Attribute::FollowRange => &Attributes::FOLLOW_RANGE,
Attribute::FrictionModifier => &Attributes::FRICTION_MODIFIER,
Attribute::Gravity => &Attributes::GRAVITY,
Attribute::JumpStrength => &Attributes::JUMP_STRENGTH,
Attribute::KnockbackResistance => &Attributes::KNOCKBACK_RESISTANCE,
Attribute::Luck => &Attributes::LUCK,
Attribute::MaxAbsorption => &Attributes::MAX_ABSORPTION,
Attribute::MaxHealth => &Attributes::MAX_HEALTH,
Attribute::MiningEfficiency => &Attributes::MINING_EFFICIENCY,
Attribute::MovementEfficiency => &Attributes::MOVEMENT_EFFICIENCY,
Attribute::MovementSpeed => &Attributes::MOVEMENT_SPEED,
Attribute::NameTagDistance => &Attributes::NAME_TAG_DISTANCE,
Attribute::OxygenBonus => &Attributes::OXYGEN_BONUS,
Attribute::SafeFallDistance => &Attributes::SAFE_FALL_DISTANCE,
Attribute::Scale => &Attributes::SCALE,
Attribute::SneakingSpeed => &Attributes::SNEAKING_SPEED,
Attribute::SpawnReinforcements => &Attributes::SPAWN_REINFORCEMENTS,
Attribute::StepHeight => &Attributes::STEP_HEIGHT,
Attribute::SubmergedMiningSpeed => &Attributes::SUBMERGED_MINING_SPEED,
Attribute::SweepingDamageRatio => &Attributes::SWEEPING_DAMAGE_RATIO,
Attribute::TemptRange => &Attributes::TEMPT_RANGE,
Attribute::WaterMovementEfficiency => &Attributes::WATER_MOVEMENT_EFFICIENCY,
Attribute::WaypointTransmitRange => &Attributes::WAYPOINT_TRANSMIT_RANGE,
Attribute::WaypointReceiveRange => &Attributes::WAYPOINT_RECEIVE_RANGE,
}
}
#[must_use]
pub const fn from_wit_modifier_op(
op: WitModifierOperation,
) -> crate::entity::attributes::ModifierOperation {
match op {
WitModifierOperation::Add => crate::entity::attributes::ModifierOperation::Add,
WitModifierOperation::MultiplyBase => {
crate::entity::attributes::ModifierOperation::MultiplyBase
}
WitModifierOperation::MultiplyTotal => {
crate::entity::attributes::ModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn to_wit_modifier_op(
op: crate::entity::attributes::ModifierOperation,
) -> WitModifierOperation {
match op {
crate::entity::attributes::ModifierOperation::Add => WitModifierOperation::Add,
crate::entity::attributes::ModifierOperation::MultiplyBase => {
WitModifierOperation::MultiplyBase
}
crate::entity::attributes::ModifierOperation::MultiplyTotal => {
WitModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn from_wit_equipment_slot(
slot: WitEquipmentSlot,
) -> pumpkin_data::data_component_impl::EquipmentSlot {
use pumpkin_data::data_component_impl::EquipmentSlot;
match slot {
WitEquipmentSlot::MainHand => EquipmentSlot::MAIN_HAND,
WitEquipmentSlot::OffHand => EquipmentSlot::OFF_HAND,
WitEquipmentSlot::Feet => EquipmentSlot::FEET,
WitEquipmentSlot::Legs => EquipmentSlot::LEGS,
WitEquipmentSlot::Chest => EquipmentSlot::CHEST,
WitEquipmentSlot::Head => EquipmentSlot::HEAD,
WitEquipmentSlot::Body => EquipmentSlot::BODY,
WitEquipmentSlot::Saddle => EquipmentSlot::SADDLE,
}
}
#[must_use]
pub const fn to_wit_damage_type(damage_type: &pumpkin_data::damage::DamageType) -> WitDamageType {
// SAFETY: WIT enum is generated in the same order as the internal enum / id
unsafe { std::mem::transmute(damage_type.id) }
}
#[must_use]
pub fn from_wit_damage_type(wit: WitDamageType) -> pumpkin_data::damage::DamageType {
pumpkin_data::damage::DamageType::from_id(wit as u8)
.unwrap_or(pumpkin_data::damage::DamageType::GENERIC)
}
impl HostEntity for PluginHostState {
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
let entity = entity_from_resource(self, &entity)?;
@@ -526,300 +411,6 @@ impl HostEntity for PluginHostState {
Ok(())
}
async fn get_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.health.load()))
}
async fn set_health(&mut self, entity: Resource<Entity>, health: f32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.health.store(health);
}
Ok(())
}
async fn get_max_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, crate::entity::living::LivingEntity::get_max_health))
}
async fn damage(
&mut self,
entity: Resource<Entity>,
amount: f32,
damage_type: WitDamageType,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.damage(&*entity, amount, from_wit_damage_type(damage_type));
Ok(())
}
async fn is_dead(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_living_entity().map_or_else(
|| entity.get_entity().removal_reason.load().is_some(),
|living| living.dead.load(std::sync::atomic::Ordering::Relaxed),
))
}
async fn get_absorption(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.absorption.load()))
}
async fn set_absorption(
&mut self,
entity: Resource<Entity>,
amount: f32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.absorption.store(amount);
}
Ok(())
}
async fn get_attribute_value(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_value(attribute)
}))
}
async fn get_attribute_base(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_base(attribute)
}))
}
async fn set_attribute_base(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
value: f64,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.set_attribute_base(attribute, value);
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn add_attribute_modifier(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
modifier: WitAttributeModifier,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let internal_mod = crate::entity::attributes::Modifier {
id: modifier.id,
amount: modifier.amount,
operation: from_wit_modifier_op(modifier.operation),
};
living.update_attribute(attribute, |inst| inst.add_or_replace_modifier(internal_mod));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn remove_attribute_modifier(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
id: String,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.update_attribute(attribute, |inst| inst.remove_modifier(&id));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn get_attribute_modifiers(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<Vec<WitAttributeModifier>> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let map = living
.attributes
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inst) = map.get(&attribute.id) {
return Ok(inst
.modifiers
.iter()
.map(|m| WitAttributeModifier {
id: m.id.clone(),
amount: m.amount,
operation: to_wit_modifier_op(m.operation),
})
.collect());
}
}
Ok(Vec::new())
}
async fn reset_attribute(
&mut self,
entity: Resource<Entity>,
attr: Attribute,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
{
let mut map = living
.attributes
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&attribute.id);
};
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn reset_all_attributes(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.reset_effects_and_attributes();
}
Ok(())
}
async fn get_equipment(
&mut self,
entity: Resource<Entity>,
slot: WitEquipmentSlot,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let stack = equipment.get(&slot);
if !stack.is_empty() {
return Ok(Some(
self.add_item_stack(Arc::new(tokio::sync::Mutex::new(stack)))?,
));
}
}
Ok(None)
}
async fn set_equipment(
&mut self,
entity: Resource<Entity>,
slot: WitEquipmentSlot,
stack: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let item_stack = if let Some(stack_res) = stack {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
{
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&slot, item_stack.clone());
};
living.send_equipment_changes(&[(slot, item_stack)]);
}
Ok(())
}
async fn clear_equipment(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let slots_to_clear: Vec<(
pumpkin_data::data_component_impl::EquipmentSlot,
pumpkin_data::item_stack::ItemStack,
)> = equipment
.equipment
.drain()
.map(|(slot, _)| (slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone()))
.collect();
drop(equipment);
living.send_equipment_changes(&slots_to_clear);
}
Ok(())
}
async fn get_age(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.age
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_age(&mut self, entity: Resource<Entity>, age: i32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.get_entity()
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
async fn get_fall_distance(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
@@ -1155,144 +746,12 @@ impl HostEntity for PluginHostState {
Ok(crate::entity::breath::MAX_AIR)
}
async fn send_system_message(
&mut self,
entity: Resource<Entity>,
message: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(player) = entity.get_player() {
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&Resource::new_own(message.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?;
player.send_system_message(&text_res.provider);
}
Ok(())
}
async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().remove();
Ok(())
}
async fn add_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
match goal {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::Swim => {
mob_entity.add_goal(priority, crate::entity::ai::goal::swim::SwimGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::WanderAround(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::wander_around::WanderAroundGoal::new(f64::from(speed)));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::MeleeAttack(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::melee_attack::MeleeAttackGoal::new(f64::from(speed), false));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAtPlayer(range) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_at_entity::LookAtEntityGoal::new(
std::sync::Weak::<crate::entity::mob::zombie::zombie::ZombieEntity>::new() as std::sync::Weak<dyn crate::entity::mob::Mob>,
&pumpkin_data::entity::EntityType::PLAYER,
range,
0.02,
false,
));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAround => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_around::RandomLookAroundGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::EscapeDanger(speed) => {
mob_entity.add_goal(priority, *crate::entity::ai::goal::escape_danger::EscapeDangerGoal::new(f64::from(speed)));
}
_ => {} // Remaining goals
}
}
Ok(())
}
async fn add_custom_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal_id: u32,
) -> wasmtime::Result<()> {
let Some(plugin) = self.plugin.as_ref().and_then(std::sync::Weak::upgrade) else {
return Err(wasmtime::Error::msg("Plugin not active"));
};
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
mob_entity.add_goal(priority, CustomWasmGoal { plugin, goal_id });
}
Ok(())
}
async fn clear_ai_goals(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().clear_ai_goals(mob);
}
Ok(())
}
async fn set_ai_disabled(
&mut self,
entity: Resource<Entity>,
disabled: bool,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_no_ai(disabled);
}
Ok(())
}
async fn is_ai_disabled(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_mob()
.is_none_or(|mob| mob.get_mob_entity().is_no_ai()))
}
async fn set_target(
&mut self,
entity: Resource<Entity>,
target: Option<Resource<Entity>>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let target_entity = if let Some(t) = target {
Some(entity_from_resource(self, &t)?)
} else {
None
};
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_target(target_entity);
}
Ok(())
}
async fn get_target(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob()
&& let Some(target) = mob.get_mob_entity().get_target()
{
return Ok(Some(self.add_entity(target)?));
}
Ok(None)
}
async fn raycast(
&mut self,
entity: Resource<Entity>,
@@ -1429,6 +888,40 @@ impl HostEntity for PluginHostState {
Ok(base_entity.has_custom_data(&namespace, &key))
}
async fn as_living(
&mut self,
this: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<WitLivingEntity>>> {
let entity = entity_from_resource(self, &this)?;
if entity.get_living_entity().is_some() {
Ok(Some(self.add_living_entity(entity)?))
} else {
Ok(None)
}
}
async fn as_mob(
&mut self,
this: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<WitMob>>> {
let entity = entity_from_resource(self, &this)?;
if entity.get_mob().is_some() {
Ok(Some(self.add_mob(entity)?))
} else {
Ok(None)
}
}
async fn is_living(&mut self, this: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().is_some())
}
async fn is_mob(&mut self, this: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &this)?;
Ok(entity.get_mob().is_some())
}
async fn drop(&mut self, rep: Resource<Entity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
@@ -1436,161 +929,3 @@ impl HostEntity for PluginHostState {
Ok(())
}
}
pub struct CustomWasmGoal {
pub plugin: Arc<WasmPlugin>,
pub goal_id: u32,
}
fn current_mob_entity(mob: &dyn Mob) -> Option<Arc<dyn crate::entity::EntityBase>> {
let entity = mob.get_entity();
entity.world.load().get_entity_by_id(entity.entity_id)
}
impl Goal for CustomWasmGoal {
fn can_start(&mut self, _mob: &dyn Mob) -> bool {
false
}
fn should_continue(&self, _mob: &dyn Mob) -> bool {
false
}
fn start(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn tick(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn stop(&mut self, mob: &dyn Mob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
}

View File

@@ -67,12 +67,12 @@ use crate::plugin::{
loader::wasm::wasm_host::{
state::PluginHostState,
wit::v0_1::{
entity::{from_wit_damage_type, to_wit_damage_type},
events::{
ToFromWasmEvent, cleanup_event, consume_player, consume_text_component,
consume_world, from_wasm_block_position, from_wasm_position,
to_wasm_block_position, to_wasm_position,
},
living_entity::{from_wit_damage_type, to_wit_damage_type},
pumpkin::plugin::event::{
AreaEffectCloudApplyEventData, ArrowBodyCountChangeEventData,
BatToggleSleepEventData, CreatureSpawnEventData, CreeperPowerEventData,

View File

@@ -118,6 +118,23 @@ impl gui::HostGui for PluginHostState {
self.add_gui(gui)
}
async fn get_inventory(
&mut self,
res: Resource<Gui>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let inv = {
let gui = self.get_gui_res(&res)?.provider.lock().await;
gui.inventory.clone()
};
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::Generic(inv),
)
}
async fn set_item(
&mut self,
res: Resource<Gui>,

View File

@@ -0,0 +1,568 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use wasmtime::component::Resource;
use crate::entity::player::Player;
use crate::plugin::loader::wasm::wasm_host::{
state::{InventoryProvider, InventoryResource, PlayerInventoryResource, PluginHostState},
wit::v0_1::pumpkin::plugin::{
common::Hand as WitHand,
inventory::{
Host as InventoryHost, HostInventory, HostPlayerInventory, Inventory as WitInventory,
PlayerInventory as WitPlayerInventory,
},
item_stack::ItemStack as WitHostItemStack,
},
};
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_protocol::java::client::play::CSetContainerSlot;
use pumpkin_world::inventory::{Clearable, Inventory};
const fn from_wasm_hand(hand: WitHand) -> pumpkin_util::Hand {
match hand {
WitHand::Right => pumpkin_util::Hand::Right,
WitHand::Left => pumpkin_util::Hand::Left,
}
}
impl InventoryHost for PluginHostState {}
impl PluginHostState {
fn get_inventory_provider(
&self,
res: &Resource<WitInventory>,
) -> wasmtime::Result<InventoryProvider> {
let r = self
.resource_table
.get::<InventoryResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)?;
Ok(r.provider.clone())
}
fn get_player_inventory_player(
&self,
res: &Resource<WitPlayerInventory>,
) -> wasmtime::Result<Arc<Player>> {
let r = self
.resource_table
.get::<PlayerInventoryResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)?;
Ok(r.provider.clone())
}
}
impl HostInventory for PluginHostState {
async fn get_size(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<u32> {
let provider = self.get_inventory_provider(&res)?;
let size = match provider {
InventoryProvider::Generic(inv) => inv.size() as u32,
InventoryProvider::PlayerMain(_) => 36,
InventoryProvider::PlayerEnderChest(_) => 27,
};
Ok(size)
}
async fn is_empty(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<bool> {
let provider = self.get_inventory_provider(&res)?;
let empty = match provider {
InventoryProvider::Generic(inv) => inv.is_empty(),
InventoryProvider::PlayerMain(player) => {
let inv = player.inventory();
(0..36).all(|slot| inv.get_stack(slot).is_empty())
}
InventoryProvider::PlayerEnderChest(player) => {
let ec = player.ender_chest_inventory();
(0..27).all(|slot| ec.get_stack(slot).is_empty())
}
};
Ok(empty)
}
async fn get_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let provider = self.get_inventory_provider(&res)?;
let stack = match provider {
InventoryProvider::Generic(inv) => {
let s = inv.get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
let s = player.inventory().get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
let s = player.ender_chest_inventory().get_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
};
if let Some(stack) = stack {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
} else {
Ok(None)
}
}
async fn set_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
let provider = self.get_inventory_provider(&res)?;
match provider {
InventoryProvider::Generic(inv) => {
inv.set_stack(slot as usize, stack);
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
player.inventory().set_stack(slot as usize, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer);
player.send_client_packet(&packet).await;
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
player
.ender_chest_inventory()
.set_stack(slot as usize, stack);
}
}
}
Ok(())
}
async fn remove_item(
&mut self,
res: Resource<WitInventory>,
slot: u32,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let provider = self.get_inventory_provider(&res)?;
let old_stack = match provider {
InventoryProvider::Generic(inv) => {
let s = inv.remove_stack(slot as usize);
if s.is_empty() { None } else { Some(s) }
}
InventoryProvider::PlayerMain(player) => {
if slot < 36 {
let s = player.inventory().get_stack(slot as usize);
player.inventory().set_stack(
slot as usize,
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let empty_serializer = ItemStackSerializer::from(
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &empty_serializer);
player.send_client_packet(&packet).await;
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
InventoryProvider::PlayerEnderChest(player) => {
if slot < 27 {
let s = player.ender_chest_inventory().get_stack(slot as usize);
player.ender_chest_inventory().set_stack(
slot as usize,
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
if s.is_empty() { None } else { Some(s) }
} else {
None
}
}
};
if let Some(stack) = old_stack {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
} else {
Ok(None)
}
}
async fn clear(&mut self, res: Resource<WitInventory>) -> wasmtime::Result<()> {
let provider = self.get_inventory_provider(&res)?;
match provider {
InventoryProvider::Generic(inv) => {
inv.clear();
}
InventoryProvider::PlayerMain(player) => {
for slot in 0..36 {
player
.inventory()
.set_stack(slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone());
let empty_serializer = ItemStackSerializer::from(
pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &empty_serializer);
player.send_client_packet(&packet).await;
}
}
InventoryProvider::PlayerEnderChest(player) => {
player.ender_chest_inventory().clear();
}
}
Ok(())
}
async fn get_all_items(
&mut self,
res: Resource<WitInventory>,
) -> wasmtime::Result<Vec<Option<Resource<WitHostItemStack>>>> {
let size = self.get_size(Resource::new_own(res.rep())).await?;
let mut items = Vec::with_capacity(size as usize);
for slot in 0..size {
let item = self.get_item(Resource::new_own(res.rep()), slot).await?;
items.push(item);
}
Ok(items)
}
async fn set_all_items(
&mut self,
res: Resource<WitInventory>,
items: Vec<Option<Resource<WitHostItemStack>>>,
) -> wasmtime::Result<()> {
let size = self.get_size(Resource::new_own(res.rep())).await?;
for (slot, item) in items.into_iter().take(size as usize).enumerate() {
self.set_item(Resource::new_own(res.rep()), slot as u32, item)
.await?;
}
Ok(())
}
async fn count_item(
&mut self,
res: Resource<WitInventory>,
item_id: String,
) -> wasmtime::Result<u32> {
let provider = self.get_inventory_provider(&res)?;
let mut total = 0u32;
let is_matching =
|key: &str| key == item_id || key.strip_prefix("minecraft:") == Some(&item_id);
match provider {
InventoryProvider::Generic(inv) => {
for slot in 0..inv.size() {
let s = inv.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
InventoryProvider::PlayerMain(player) => {
let inv = player.inventory();
for slot in 0..36 {
let s = inv.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
InventoryProvider::PlayerEnderChest(player) => {
let ec = player.ender_chest_inventory();
for slot in 0..27 {
let s = ec.get_stack(slot);
if !s.is_empty() && is_matching(s.item.registry_key) {
total += u32::from(s.item_count);
}
}
}
}
Ok(total)
}
async fn contains_item(
&mut self,
res: Resource<WitInventory>,
item_id: String,
) -> wasmtime::Result<bool> {
let count = self.count_item(res, item_id).await?;
Ok(count > 0)
}
async fn drop(&mut self, rep: Resource<WitInventory>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<InventoryResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostPlayerInventory for PluginHostState {
async fn as_inventory(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Resource<WitInventory>> {
let player = self.get_player_inventory_player(&res)?;
self.add_inventory(InventoryProvider::PlayerMain(player))
}
async fn get_item_in_hand(
&mut self,
res: Resource<WitPlayerInventory>,
hand: WitHand,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let hand = from_wasm_hand(hand);
let stack = player.inventory().get_stack_in_hand(hand);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_item_in_hand(
&mut self,
res: Resource<WitPlayerInventory>,
hand: WitHand,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
let hand = from_wasm_hand(hand);
let slot = match hand {
pumpkin_util::Hand::Right => player.inventory().get_selected_slot() as usize,
pumpkin_util::Hand::Left => PlayerInventory::OFF_HAND_SLOT,
};
player.inventory().set_stack(slot, stack.clone());
// Sync to client
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_selected_slot(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<u8> {
let player = self.get_player_inventory_player(&res)?;
Ok(player.inventory().get_selected_slot())
}
async fn set_selected_slot(
&mut self,
res: Resource<WitPlayerInventory>,
slot: u8,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
if slot < 9 {
player.inventory().set_selected_slot(slot);
}
Ok(())
}
async fn get_helmet(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(39);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_helmet(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(39, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 5, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_chestplate(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(38);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_chestplate(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(38, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 6, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_leggings(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(37);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_leggings(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(37, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 7, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_boots(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(36);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_boots(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(36, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 8, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn get_off_hand(
&mut self,
res: Resource<WitPlayerInventory>,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = self.get_player_inventory_player(&res)?;
let stack = player.inventory().get_slot(40);
if stack.is_empty() {
Ok(None)
} else {
Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
}
}
async fn set_off_hand(
&mut self,
res: Resource<WitPlayerInventory>,
item: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let player = self.get_player_inventory_player(&res)?;
let stack = if let Some(stack_res) = item {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player.inventory().set_slot(40, stack.clone());
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(0, 0, 45, &stack_serializer);
player.send_client_packet(&packet).await;
Ok(())
}
async fn clear_armor(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
self.set_helmet(Resource::new_own(res.rep()), None).await?;
self.set_chestplate(Resource::new_own(res.rep()), None)
.await?;
self.set_leggings(Resource::new_own(res.rep()), None)
.await?;
self.set_boots(Resource::new_own(res.rep()), None).await?;
Ok(())
}
async fn clear_main(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
let inv = self.as_inventory(res).await?;
self.clear(inv).await
}
async fn clear_all(&mut self, res: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
self.clear_main(Resource::new_own(res.rep())).await?;
self.clear_armor(Resource::new_own(res.rep())).await?;
self.set_off_hand(Resource::new_own(res.rep()), None)
.await?;
Ok(())
}
async fn drop(&mut self, rep: Resource<WitPlayerInventory>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<PlayerInventoryResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -0,0 +1,502 @@
use std::sync::Arc;
use wasmtime::component::Resource;
use crate::plugin::loader::wasm::wasm_host::{
state::{LivingEntityResource, PluginHostState},
wit::v0_1::pumpkin::plugin::{
attributes::{
Attribute, AttributeModifier as WitAttributeModifier,
ModifierOperation as WitModifierOperation,
},
damage_types::DamageType as WitDamageType,
item_stack::ItemStack as WitHostItemStack,
text::TextComponent,
world::{
Entity, EquipmentSlot as WitEquipmentSlot, HostLivingEntity,
LivingEntity as WitLivingEntity, Mob as WitMob,
},
},
};
pub fn living_entity_from_resource(
state: &PluginHostState,
entity: &Resource<WitLivingEntity>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
state
.resource_table
.get::<LivingEntityResource>(&Resource::new_own(entity.rep()))
.map_err(|_| wasmtime::Error::msg("invalid living entity resource handle"))
.map(|resource| resource.provider.clone())
}
#[must_use]
pub const fn from_wit_attribute(attr: Attribute) -> &'static pumpkin_data::attributes::Attributes {
use pumpkin_data::attributes::Attributes;
match attr {
Attribute::AirDragModifier => &Attributes::AIR_DRAG_MODIFIER,
Attribute::Armor => &Attributes::ARMOR,
Attribute::ArmorToughness => &Attributes::ARMOR_TOUGHNESS,
Attribute::AttackDamage => &Attributes::ATTACK_DAMAGE,
Attribute::AttackKnockback => &Attributes::ATTACK_KNOCKBACK,
Attribute::AttackSpeed => &Attributes::ATTACK_SPEED,
Attribute::BelowNameDistance => &Attributes::BELOW_NAME_DISTANCE,
Attribute::BlockBreakSpeed => &Attributes::BLOCK_BREAK_SPEED,
Attribute::BlockInteractionRange => &Attributes::BLOCK_INTERACTION_RANGE,
Attribute::Bounciness => &Attributes::BOUNCINESS,
Attribute::BurningTime => &Attributes::BURNING_TIME,
Attribute::CameraDistance => &Attributes::CAMERA_DISTANCE,
Attribute::ExplosionKnockbackResistance => &Attributes::EXPLOSION_KNOCKBACK_RESISTANCE,
Attribute::EntityInteractionRange => &Attributes::ENTITY_INTERACTION_RANGE,
Attribute::FallDamageMultiplier => &Attributes::FALL_DAMAGE_MULTIPLIER,
Attribute::FlyingSpeed => &Attributes::FLYING_SPEED,
Attribute::FollowRange => &Attributes::FOLLOW_RANGE,
Attribute::FrictionModifier => &Attributes::FRICTION_MODIFIER,
Attribute::Gravity => &Attributes::GRAVITY,
Attribute::JumpStrength => &Attributes::JUMP_STRENGTH,
Attribute::KnockbackResistance => &Attributes::KNOCKBACK_RESISTANCE,
Attribute::Luck => &Attributes::LUCK,
Attribute::MaxAbsorption => &Attributes::MAX_ABSORPTION,
Attribute::MaxHealth => &Attributes::MAX_HEALTH,
Attribute::MiningEfficiency => &Attributes::MINING_EFFICIENCY,
Attribute::MovementEfficiency => &Attributes::MOVEMENT_EFFICIENCY,
Attribute::MovementSpeed => &Attributes::MOVEMENT_SPEED,
Attribute::NameTagDistance => &Attributes::NAME_TAG_DISTANCE,
Attribute::OxygenBonus => &Attributes::OXYGEN_BONUS,
Attribute::SafeFallDistance => &Attributes::SAFE_FALL_DISTANCE,
Attribute::Scale => &Attributes::SCALE,
Attribute::SneakingSpeed => &Attributes::SNEAKING_SPEED,
Attribute::SpawnReinforcements => &Attributes::SPAWN_REINFORCEMENTS,
Attribute::StepHeight => &Attributes::STEP_HEIGHT,
Attribute::SubmergedMiningSpeed => &Attributes::SUBMERGED_MINING_SPEED,
Attribute::SweepingDamageRatio => &Attributes::SWEEPING_DAMAGE_RATIO,
Attribute::TemptRange => &Attributes::TEMPT_RANGE,
Attribute::WaterMovementEfficiency => &Attributes::WATER_MOVEMENT_EFFICIENCY,
Attribute::WaypointTransmitRange => &Attributes::WAYPOINT_TRANSMIT_RANGE,
Attribute::WaypointReceiveRange => &Attributes::WAYPOINT_RECEIVE_RANGE,
}
}
#[must_use]
pub const fn from_wit_modifier_op(
op: WitModifierOperation,
) -> crate::entity::attributes::ModifierOperation {
match op {
WitModifierOperation::Add => crate::entity::attributes::ModifierOperation::Add,
WitModifierOperation::MultiplyBase => {
crate::entity::attributes::ModifierOperation::MultiplyBase
}
WitModifierOperation::MultiplyTotal => {
crate::entity::attributes::ModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn to_wit_modifier_op(
op: crate::entity::attributes::ModifierOperation,
) -> WitModifierOperation {
match op {
crate::entity::attributes::ModifierOperation::Add => WitModifierOperation::Add,
crate::entity::attributes::ModifierOperation::MultiplyBase => {
WitModifierOperation::MultiplyBase
}
crate::entity::attributes::ModifierOperation::MultiplyTotal => {
WitModifierOperation::MultiplyTotal
}
}
}
#[must_use]
pub const fn from_wit_equipment_slot(
slot: WitEquipmentSlot,
) -> pumpkin_data::data_component_impl::EquipmentSlot {
use pumpkin_data::data_component_impl::EquipmentSlot;
match slot {
WitEquipmentSlot::MainHand => EquipmentSlot::MAIN_HAND,
WitEquipmentSlot::OffHand => EquipmentSlot::OFF_HAND,
WitEquipmentSlot::Feet => EquipmentSlot::FEET,
WitEquipmentSlot::Legs => EquipmentSlot::LEGS,
WitEquipmentSlot::Chest => EquipmentSlot::CHEST,
WitEquipmentSlot::Head => EquipmentSlot::HEAD,
WitEquipmentSlot::Body => EquipmentSlot::BODY,
WitEquipmentSlot::Saddle => EquipmentSlot::SADDLE,
}
}
#[must_use]
pub const fn to_wit_damage_type(damage_type: &pumpkin_data::damage::DamageType) -> WitDamageType {
// SAFETY: WIT enum is generated in the same order as the internal enum / id
unsafe { std::mem::transmute(damage_type.id) }
}
#[must_use]
pub fn from_wit_damage_type(wit: WitDamageType) -> pumpkin_data::damage::DamageType {
pumpkin_data::damage::DamageType::from_id(wit as u8)
.unwrap_or(pumpkin_data::damage::DamageType::GENERIC)
}
impl HostLivingEntity for PluginHostState {
async fn as_entity(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<Resource<Entity>> {
let entity = living_entity_from_resource(self, &this)?;
self.add_entity(entity)
}
async fn as_mob(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<Option<Resource<WitMob>>> {
let entity = living_entity_from_resource(self, &this)?;
if entity.get_mob().is_some() {
Ok(Some(self.add_mob(entity)?))
} else {
Ok(None)
}
}
async fn is_mob(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<bool> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_mob().is_some())
}
async fn get_health(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.health.load()))
}
async fn set_health(
&mut self,
this: Resource<WitLivingEntity>,
health: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.health.store(health);
}
Ok(())
}
async fn get_max_health(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, crate::entity::living::LivingEntity::get_max_health))
}
async fn set_max_health(
&mut self,
this: Resource<WitLivingEntity>,
max_health: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.set_max_health(max_health);
}
Ok(())
}
async fn damage(
&mut self,
this: Resource<WitLivingEntity>,
amount: f32,
damage_type: WitDamageType,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
entity.damage(&*entity, amount, from_wit_damage_type(damage_type));
Ok(())
}
async fn is_dead(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<bool> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().map_or_else(
|| entity.get_entity().removal_reason.load().is_some(),
|living| living.dead.load(std::sync::atomic::Ordering::Relaxed),
))
}
async fn get_absorption(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<f32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.absorption.load()))
}
async fn set_absorption(
&mut self,
this: Resource<WitLivingEntity>,
amount: f32,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.absorption.store(amount);
}
Ok(())
}
async fn get_attribute_value(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_value(attribute)
}))
}
async fn get_attribute_base(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<f64> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
Ok(entity
.get_living_entity()
.map_or(attribute.default_value, |living| {
living.get_attribute_base(attribute)
}))
}
async fn set_attribute_base(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
value: f64,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.set_attribute_base(attribute, value);
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn add_attribute_modifier(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
modifier: WitAttributeModifier,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let internal_mod = crate::entity::attributes::Modifier {
id: modifier.id,
amount: modifier.amount,
operation: from_wit_modifier_op(modifier.operation),
};
living.update_attribute(attribute, |inst| inst.add_or_replace_modifier(internal_mod));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn remove_attribute_modifier(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
id: String,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
living.update_attribute(attribute, |inst| inst.remove_modifier(&id));
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn get_attribute_modifiers(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<Vec<WitAttributeModifier>> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
let map = living
.attributes
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inst) = map.get(&attribute.id) {
return Ok(inst
.modifiers
.iter()
.map(|m| WitAttributeModifier {
id: m.id.clone(),
amount: m.amount,
operation: to_wit_modifier_op(m.operation),
})
.collect());
}
}
Ok(Vec::new())
}
async fn reset_attribute(
&mut self,
this: Resource<WitLivingEntity>,
attr: Attribute,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() {
{
let mut map = living
.attributes
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&attribute.id);
};
crate::entity::attributes::send_attribute_updates_for_living(
living,
vec![attribute.clone()],
);
}
Ok(())
}
async fn reset_all_attributes(
&mut self,
this: Resource<WitLivingEntity>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living.reset_effects_and_attributes();
}
Ok(())
}
async fn get_equipment(
&mut self,
this: Resource<WitLivingEntity>,
slot: WitEquipmentSlot,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let stack = equipment.get(&slot);
if !stack.is_empty() {
return Ok(Some(
self.add_item_stack(Arc::new(tokio::sync::Mutex::new(stack)))?,
));
}
}
Ok(None)
}
async fn set_equipment(
&mut self,
this: Resource<WitLivingEntity>,
slot: WitEquipmentSlot,
stack: Option<Resource<WitHostItemStack>>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let slot = from_wit_equipment_slot(slot);
let item_stack = if let Some(stack_res) = stack {
self.get_item_stack(&stack_res)?.lock().await.clone()
} else {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
{
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&slot, item_stack.clone());
};
living.send_equipment_changes(&[(slot, item_stack)]);
}
Ok(())
}
async fn clear_equipment(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let slots_to_clear: Vec<(
pumpkin_data::data_component_impl::EquipmentSlot,
pumpkin_data::item_stack::ItemStack,
)> = equipment
.equipment
.drain()
.map(|(slot, _)| (slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone()))
.collect();
drop(equipment);
living.send_equipment_changes(&slots_to_clear);
}
Ok(())
}
async fn get_age(&mut self, this: Resource<WitLivingEntity>) -> wasmtime::Result<i32> {
let entity = living_entity_from_resource(self, &this)?;
Ok(entity.get_living_entity().map_or(0, |living| {
living.entity.age.load(std::sync::atomic::Ordering::Relaxed)
}))
}
async fn set_age(&mut self, this: Resource<WitLivingEntity>, age: i32) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(living) = entity.get_living_entity() {
living
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
}
Ok(())
}
async fn send_system_message(
&mut self,
this: Resource<WitLivingEntity>,
message: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity = living_entity_from_resource(self, &this)?;
if let Some(player) = entity.get_player() {
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&Resource::new_own(message.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?;
player.send_system_message(&text_res.provider);
}
Ok(())
}
async fn drop(&mut self, rep: Resource<WitLivingEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<LivingEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -0,0 +1,941 @@
use std::sync::Arc;
use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3;
use crate::entity::ai::goal::Goal;
use crate::entity::mob::Mob as InternalMob;
use crate::entity::passive::tamable::TamableAnimal;
use crate::plugin::loader::wasm::wasm_host::{
PluginInstance, WasmPlugin,
state::{MobResource, PluginHostState},
wit::v0_1::entity::entity_from_resource,
wit::v0_1::pumpkin::plugin::{
common::Position,
uuid::Uuid,
world::{
AgeableData as WitAgeableData, BlockDirection as WitBlockDirection,
CatData as WitCatData, CreeperData as WitCreeperData, DyeColor as WitDyeColor,
EndermanData as WitEndermanData, Entity, FoxData as WitFoxData, HostMob,
IronGolemData as WitIronGolemData, LivingEntity as WitLivingEntity, Mob as WitMob,
MobData as WitMobData, PathNodeType as WitPathNodeType, SheepData as WitSheepData,
ShulkerData as WitShulkerData, SlimeData as WitSlimeData,
VillagerData as WitVillagerData, VillagerProfession as WitVillagerProfession,
WolfData as WitWolfData, ZombieData as WitZombieData,
},
},
wit::v0_1::uuid::UuidExt,
};
pub fn mob_from_resource(
state: &PluginHostState,
entity: &Resource<WitMob>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
state
.resource_table
.get::<MobResource>(&Resource::new_own(entity.rep()))
.map_err(|_| wasmtime::Error::msg("invalid mob resource handle"))
.map(|resource| resource.provider.clone())
}
#[must_use]
pub const fn from_wit_path_node_type(
t: WitPathNodeType,
) -> crate::entity::ai::pathfinder::node::PathType {
use crate::entity::ai::pathfinder::node::PathType;
match t {
WitPathNodeType::Blocked => PathType::Blocked,
WitPathNodeType::Open => PathType::Open,
WitPathNodeType::Walkable => PathType::Walkable,
WitPathNodeType::WalkableDoor => PathType::WalkableDoor,
WitPathNodeType::Trapdoor => PathType::Trapdoor,
WitPathNodeType::PowderSnow => PathType::PowderSnow,
WitPathNodeType::DangerPowderSnow => PathType::DangerPowderSnow,
WitPathNodeType::Fence => PathType::Fence,
WitPathNodeType::Lava => PathType::Lava,
WitPathNodeType::Water => PathType::Water,
WitPathNodeType::WaterBorder => PathType::WaterBorder,
WitPathNodeType::Rail => PathType::Rail,
WitPathNodeType::UnpassableRail => PathType::UnpassableRail,
WitPathNodeType::DangerFire => PathType::DangerFire,
WitPathNodeType::DamageFire => PathType::DamageFire,
WitPathNodeType::DangerOther => PathType::DangerOther,
WitPathNodeType::DamageOther => PathType::DamageOther,
WitPathNodeType::DoorOpen => PathType::DoorOpen,
WitPathNodeType::DoorWoodClosed => PathType::DoorWoodClosed,
WitPathNodeType::DoorIronClosed => PathType::DoorIronClosed,
WitPathNodeType::Breach => PathType::Breach,
WitPathNodeType::Leaves => PathType::Leaves,
WitPathNodeType::StickyHoney => PathType::StickyHoney,
WitPathNodeType::Cocoa => PathType::Cocoa,
WitPathNodeType::DamageCautious => PathType::DamageCautious,
WitPathNodeType::DangerTrapdoor => PathType::DangerTrapdoor,
}
}
#[must_use]
pub const fn to_wit_dye_color(color: u8) -> WitDyeColor {
match color {
0 => WitDyeColor::White,
1 => WitDyeColor::Orange,
2 => WitDyeColor::Magenta,
3 => WitDyeColor::LightBlue,
4 => WitDyeColor::Yellow,
5 => WitDyeColor::Lime,
6 => WitDyeColor::Pink,
7 => WitDyeColor::Gray,
8 => WitDyeColor::LightGray,
9 => WitDyeColor::Cyan,
10 => WitDyeColor::Purple,
11 => WitDyeColor::Blue,
12 => WitDyeColor::Brown,
13 => WitDyeColor::Green,
14 => WitDyeColor::Red,
_ => WitDyeColor::Black,
}
}
#[must_use]
pub const fn from_wit_dye_color(color: WitDyeColor) -> u8 {
match color {
WitDyeColor::White => 0,
WitDyeColor::Orange => 1,
WitDyeColor::Magenta => 2,
WitDyeColor::LightBlue => 3,
WitDyeColor::Yellow => 4,
WitDyeColor::Lime => 5,
WitDyeColor::Pink => 6,
WitDyeColor::Gray => 7,
WitDyeColor::LightGray => 8,
WitDyeColor::Cyan => 9,
WitDyeColor::Purple => 10,
WitDyeColor::Blue => 11,
WitDyeColor::Brown => 12,
WitDyeColor::Green => 13,
WitDyeColor::Red => 14,
WitDyeColor::Black => 15,
}
}
#[must_use]
pub const fn to_wit_villager_profession(prof_id: i32) -> WitVillagerProfession {
let prof = match pumpkin_data::villager::VillagerProfession::from_i32(prof_id) {
Some(p) => p,
None => pumpkin_data::villager::VillagerProfession::None,
};
match prof {
pumpkin_data::villager::VillagerProfession::None => WitVillagerProfession::None,
pumpkin_data::villager::VillagerProfession::Armorer => WitVillagerProfession::Armorer,
pumpkin_data::villager::VillagerProfession::Butcher => WitVillagerProfession::Butcher,
pumpkin_data::villager::VillagerProfession::Cartographer => {
WitVillagerProfession::Cartographer
}
pumpkin_data::villager::VillagerProfession::Cleric => WitVillagerProfession::Cleric,
pumpkin_data::villager::VillagerProfession::Farmer => WitVillagerProfession::Farmer,
pumpkin_data::villager::VillagerProfession::Fisherman => WitVillagerProfession::Fisherman,
pumpkin_data::villager::VillagerProfession::Fletcher => WitVillagerProfession::Fletcher,
pumpkin_data::villager::VillagerProfession::Leatherworker => {
WitVillagerProfession::Leatherworker
}
pumpkin_data::villager::VillagerProfession::Librarian => WitVillagerProfession::Librarian,
pumpkin_data::villager::VillagerProfession::Mason => WitVillagerProfession::Mason,
pumpkin_data::villager::VillagerProfession::Nitwit => WitVillagerProfession::Nitwit,
pumpkin_data::villager::VillagerProfession::Shepherd => WitVillagerProfession::Shepherd,
pumpkin_data::villager::VillagerProfession::Toolsmith => WitVillagerProfession::Toolsmith,
pumpkin_data::villager::VillagerProfession::Weaponsmith => {
WitVillagerProfession::Weaponsmith
}
}
}
#[must_use]
pub const fn from_wit_villager_profession(
prof: WitVillagerProfession,
) -> pumpkin_data::villager::VillagerProfession {
match prof {
WitVillagerProfession::None => pumpkin_data::villager::VillagerProfession::None,
WitVillagerProfession::Armorer => pumpkin_data::villager::VillagerProfession::Armorer,
WitVillagerProfession::Butcher => pumpkin_data::villager::VillagerProfession::Butcher,
WitVillagerProfession::Cartographer => {
pumpkin_data::villager::VillagerProfession::Cartographer
}
WitVillagerProfession::Cleric => pumpkin_data::villager::VillagerProfession::Cleric,
WitVillagerProfession::Farmer => pumpkin_data::villager::VillagerProfession::Farmer,
WitVillagerProfession::Fisherman => pumpkin_data::villager::VillagerProfession::Fisherman,
WitVillagerProfession::Fletcher => pumpkin_data::villager::VillagerProfession::Fletcher,
WitVillagerProfession::Leatherworker => {
pumpkin_data::villager::VillagerProfession::Leatherworker
}
WitVillagerProfession::Librarian => pumpkin_data::villager::VillagerProfession::Librarian,
WitVillagerProfession::Mason => pumpkin_data::villager::VillagerProfession::Mason,
WitVillagerProfession::Nitwit => pumpkin_data::villager::VillagerProfession::Nitwit,
WitVillagerProfession::Shepherd => pumpkin_data::villager::VillagerProfession::Shepherd,
WitVillagerProfession::Toolsmith => pumpkin_data::villager::VillagerProfession::Toolsmith,
WitVillagerProfession::Weaponsmith => {
pumpkin_data::villager::VillagerProfession::Weaponsmith
}
}
}
#[must_use]
pub const fn to_wit_block_direction(dir: pumpkin_data::BlockDirection) -> WitBlockDirection {
match dir {
pumpkin_data::BlockDirection::Down => WitBlockDirection::Down,
pumpkin_data::BlockDirection::Up => WitBlockDirection::Up,
pumpkin_data::BlockDirection::North => WitBlockDirection::North,
pumpkin_data::BlockDirection::South => WitBlockDirection::South,
pumpkin_data::BlockDirection::West => WitBlockDirection::West,
pumpkin_data::BlockDirection::East => WitBlockDirection::East,
}
}
#[must_use]
pub const fn from_wit_block_direction(dir: WitBlockDirection) -> pumpkin_data::BlockDirection {
match dir {
WitBlockDirection::Down => pumpkin_data::BlockDirection::Down,
WitBlockDirection::Up => pumpkin_data::BlockDirection::Up,
WitBlockDirection::North => pumpkin_data::BlockDirection::North,
WitBlockDirection::South => pumpkin_data::BlockDirection::South,
WitBlockDirection::West => pumpkin_data::BlockDirection::West,
WitBlockDirection::East => pumpkin_data::BlockDirection::East,
}
}
pub struct CustomWasmGoal {
pub plugin: Arc<WasmPlugin>,
pub goal_id: u32,
}
fn current_mob_entity(mob: &dyn InternalMob) -> Option<Arc<dyn crate::entity::EntityBase>> {
let entity = mob.get_entity();
entity.world.load().get_entity_by_id(entity.entity_id)
}
impl Goal for CustomWasmGoal {
fn can_start(&mut self, _mob: &dyn InternalMob) -> bool {
false
}
fn should_continue(&self, _mob: &dyn InternalMob) -> bool {
false
}
fn start(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn tick(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
fn stop(&mut self, mob: &dyn InternalMob) {
if let Some(entity_arc) = current_mob_entity(mob) {
let plugin = self.plugin.clone();
let goal_id = self.goal_id;
tokio::spawn(async move {
let mut store = plugin.store.lock().await;
match plugin.plugin_instance {
PluginInstance::V0_1(ref plugin_inst) => {
let Some(server) = store.data_mut().server.clone() else {
return;
};
let Ok(server_res) = store.data_mut().add_server(server) else {
return;
};
let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else {
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_res.rep()),
);
return;
};
let server_rep = server_res.rep();
let entity_rep = entity_res.rep();
let _ = plugin_inst
.call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res)
.await;
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
});
}
}
}
impl HostMob for PluginHostState {
async fn as_entity(&mut self, this: Resource<WitMob>) -> wasmtime::Result<Resource<Entity>> {
let entity = mob_from_resource(self, &this)?;
self.add_entity(entity)
}
async fn as_living(
&mut self,
this: Resource<WitMob>,
) -> wasmtime::Result<Resource<WitLivingEntity>> {
let entity = mob_from_resource(self, &this)?;
self.add_living_entity(entity)
}
async fn add_ai_goal(
&mut self,
this: Resource<WitMob>,
priority: u8,
goal: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
match goal {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::Swim => {
mob_entity.add_goal(priority, crate::entity::ai::goal::swim::SwimGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::WanderAround(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::wander_around::WanderAroundGoal::new(f64::from(speed)));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::MeleeAttack(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::melee_attack::MeleeAttackGoal::new(f64::from(speed), false));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAtPlayer(range) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_at_entity::LookAtEntityGoal::new(
std::sync::Weak::<crate::entity::mob::zombie::zombie::ZombieEntity>::new() as std::sync::Weak<dyn crate::entity::mob::Mob>,
&pumpkin_data::entity::EntityType::PLAYER,
range,
0.02,
false,
));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAround => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_around::RandomLookAroundGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::EscapeDanger(speed) => {
mob_entity.add_goal(priority, *crate::entity::ai::goal::escape_danger::EscapeDangerGoal::new(f64::from(speed)));
}
_ => {}
}
}
Ok(())
}
async fn add_custom_ai_goal(
&mut self,
this: Resource<WitMob>,
priority: u8,
goal_id: u32,
) -> wasmtime::Result<()> {
let Some(plugin) = self.plugin.as_ref().and_then(std::sync::Weak::upgrade) else {
return Err(wasmtime::Error::msg("Plugin not active"));
};
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
mob_entity.add_goal(priority, CustomWasmGoal { plugin, goal_id });
}
Ok(())
}
async fn clear_ai_goals(&mut self, this: Resource<WitMob>) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().clear_ai_goals(mob);
}
Ok(())
}
async fn set_ai_disabled(
&mut self,
this: Resource<WitMob>,
disabled: bool,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_no_ai(disabled);
}
Ok(())
}
async fn is_ai_disabled(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity
.get_mob()
.is_none_or(|mob| mob.get_mob_entity().is_no_ai()))
}
async fn set_target(
&mut self,
this: Resource<WitMob>,
target: Option<Resource<Entity>>,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
let target_entity = target.map(|t| entity_from_resource(self, &t)).transpose()?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_target(target_entity);
}
Ok(())
}
async fn get_target(
&mut self,
this: Resource<WitMob>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = mob_from_resource(self, &this)?;
if let Some(target) = entity
.get_mob()
.and_then(|mob| mob.get_mob_entity().get_target())
{
return Ok(Some(self.add_entity(target)?));
}
Ok(None)
}
async fn navigate_to_pos(
&mut self,
this: Resource<WitMob>,
pos: Position,
speed: f64,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let mob_pos = entity.get_entity().pos.load();
let dest = Vector3::new(pos.0, pos.1, pos.2);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_progress(crate::entity::ai::pathfinder::NavigatorGoal::new(
mob_pos, dest, speed,
));
true
}))
}
async fn navigate_to_entity(
&mut self,
this: Resource<WitMob>,
target: Resource<Entity>,
speed: f64,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
let target_entity = entity_from_resource(self, &target)?;
Ok(entity.get_mob().is_some_and(|mob| {
let mob_pos = entity.get_entity().pos.load();
let target_pos = target_entity.get_entity().pos.load();
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_progress(crate::entity::ai::pathfinder::NavigatorGoal::new(
mob_pos, target_pos, speed,
));
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at_entity(mob, &target_entity);
true
}))
}
async fn stop_navigation(&mut self, this: Resource<WitMob>) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.stop();
}
Ok(())
}
async fn is_navigating(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let is_idle = mob
.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_idle
.load(std::sync::atomic::Ordering::Relaxed);
!is_idle
}))
}
async fn has_reached_destination(&mut self, this: Resource<WitMob>) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_none_or(|mob| {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_idle
.load(std::sync::atomic::Ordering::Relaxed)
}))
}
async fn set_navigation_speed(
&mut self,
this: Resource<WitMob>,
speed: f64,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_speed(speed);
}
Ok(())
}
async fn can_reach(
&mut self,
this: Resource<WitMob>,
pos: Position,
max_distance: f32,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().is_some_and(|mob| {
let living = &mob.get_mob_entity().living_entity;
let dest = Vector3::new(pos.0, pos.1, pos.2);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.can_reach_within(living, dest, max_distance)
}))
}
async fn set_pathfinding_malus(
&mut self,
this: Resource<WitMob>,
node_type: WitPathNodeType,
malus: f32,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
let internal_type = from_wit_path_node_type(node_type);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_pathfinding_malus(internal_type, malus);
}
Ok(())
}
async fn get_pathfinding_malus(
&mut self,
this: Resource<WitMob>,
node_type: WitPathNodeType,
) -> wasmtime::Result<f32> {
let entity = mob_from_resource(self, &this)?;
Ok(entity.get_mob().map_or(0.0, |mob| {
let internal_type = from_wit_path_node_type(node_type);
mob.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get_pathfinding_malus(internal_type)
}))
}
async fn look_at(&mut self, this: Resource<WitMob>, pos: Position) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at(mob, pos.0, pos.1, pos.2);
}
Ok(())
}
async fn look_at_entity(
&mut self,
this: Resource<WitMob>,
target: Resource<Entity>,
) -> wasmtime::Result<()> {
let entity = mob_from_resource(self, &this)?;
let target_entity = entity_from_resource(self, &target)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity()
.look_control
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.look_at_entity(mob, &target_entity);
}
Ok(())
}
#[allow(clippy::too_many_lines)]
async fn get_mob_data(&mut self, this: Resource<WitMob>) -> wasmtime::Result<WitMobData> {
let entity = mob_from_resource(self, &this)?;
let any = entity.cast_any();
if let Some(sheep) = any.downcast_ref::<crate::entity::passive::sheep::SheepEntity>() {
return Ok(WitMobData::Sheep(WitSheepData {
color: to_wit_dye_color(sheep.get_color()),
is_sheared: sheep.is_sheared(),
}));
}
if let Some(wolf) = any.downcast_ref::<crate::entity::passive::wolf::WolfEntity>() {
return Ok(WitMobData::Wolf(WitWolfData {
is_tamed: wolf.is_tame(),
owner: wolf.get_owner().map(|u| Uuid::to_wit(&u)),
is_sitting: wolf.is_in_sitting_pose(),
collar_color: to_wit_dye_color(wolf.get_collar_color()),
is_angry: false,
is_begging: false,
}));
}
if let Some(cat) = any.downcast_ref::<crate::entity::passive::cat::CatEntity>() {
return Ok(WitMobData::Cat(WitCatData {
is_tamed: cat.is_tame(),
owner: cat.get_owner().map(|u| Uuid::to_wit(&u)),
is_sitting: cat.is_in_sitting_pose(),
collar_color: to_wit_dye_color(cat.get_collar_color()),
}));
}
if let Some(villager) =
any.downcast_ref::<crate::entity::passive::villager::VillagerEntity>()
{
let data = *villager
.villager_data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
return Ok(WitMobData::Villager(WitVillagerData {
profession: to_wit_villager_profession(data.profession.0),
level: (data.level.0).clamp(0, 255) as u8,
experience: villager
.xp
.load(std::sync::atomic::Ordering::Relaxed)
.max(0) as u32,
}));
}
if let Some(creeper) = any.downcast_ref::<crate::entity::mob::creeper::CreeperEntity>() {
return Ok(WitMobData::Creeper(WitCreeperData {
is_powered: creeper.is_charged(),
fuse: creeper.get_fuse(),
is_ignited: creeper.is_ignited(),
explosion_radius: creeper.get_explosion_radius().clamp(0, 255) as u8,
}));
}
if let Some(slime) = any.downcast_ref::<crate::entity::mob::slime::SlimeEntity>() {
return Ok(WitMobData::Slime(WitSlimeData {
size: slime.get_size(),
}));
}
if let Some(enderman) = any.downcast_ref::<crate::entity::mob::enderman::EndermanEntity>() {
return Ok(WitMobData::Enderman(WitEndermanData {
carried_block_state: enderman
.get_carried_block()
.map(pumpkin_data::BlockStateId::as_u16),
is_screaming: enderman.is_angry(),
is_staring: enderman.is_angry(),
}));
}
if let Some(iron_golem) =
any.downcast_ref::<crate::entity::passive::iron_golem::IronGolemEntity>()
{
return Ok(WitMobData::IronGolem(WitIronGolemData {
is_player_created: iron_golem.is_player_created(),
}));
}
if let Some(fox) = any.downcast_ref::<crate::entity::passive::fox::FoxEntity>() {
return Ok(WitMobData::Fox(WitFoxData {
is_sitting: fox.is_sitting(),
is_sleeping: fox.is_sleeping(),
is_crouching: fox.is_crouching(),
}));
}
if let Some(shulker) = any.downcast_ref::<crate::entity::mob::shulker::ShulkerEntity>() {
return Ok(WitMobData::Shulker(WitShulkerData {
attached_face: to_wit_block_direction(shulker.get_attach_face()),
peek_amount: shulker.get_raw_peek(),
color: shulker.get_color().map(to_wit_dye_color),
}));
}
if let Some(zombie) = any.downcast_ref::<crate::entity::mob::zombie::zombie::ZombieEntity>()
{
return Ok(WitMobData::Zombie(WitZombieData {
is_baby: zombie.is_baby(),
can_break_doors: zombie.can_break_doors(),
}));
}
if let Some(living) = entity.get_living_entity() {
let age = living.entity.age.load(std::sync::atomic::Ordering::Relaxed);
return Ok(WitMobData::Ageable(WitAgeableData {
is_baby: age < 0,
age,
in_love_ticks: 0,
}));
}
Ok(WitMobData::Generic)
}
#[allow(clippy::too_many_lines)]
async fn set_mob_data(
&mut self,
this: Resource<WitMob>,
data: WitMobData,
) -> wasmtime::Result<bool> {
let entity = mob_from_resource(self, &this)?;
let any = entity.cast_any();
match data {
WitMobData::Sheep(sheep_data) => {
if let Some(sheep) =
any.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
{
sheep.set_color(from_wit_dye_color(sheep_data.color));
sheep.set_sheared(sheep_data.is_sheared);
return Ok(true);
}
}
WitMobData::Wolf(wolf_data) => {
if let Some(wolf) = any.downcast_ref::<crate::entity::passive::wolf::WolfEntity>() {
wolf.set_tame(wolf_data.is_tamed);
wolf.set_owner(wolf_data.owner.map(|u| Uuid::from_wit(&u)));
wolf.set_in_sitting_pose(wolf_data.is_sitting);
wolf.set_collar_color(from_wit_dye_color(wolf_data.collar_color));
return Ok(true);
}
}
WitMobData::Cat(cat_data) => {
if let Some(cat) = any.downcast_ref::<crate::entity::passive::cat::CatEntity>() {
cat.set_tame(
cat_data.is_tamed,
cat_data.owner.map(|u| Uuid::from_wit(&u)),
);
cat.set_sitting(cat_data.is_sitting);
cat.set_collar_color(from_wit_dye_color(cat_data.collar_color));
return Ok(true);
}
}
WitMobData::Villager(villager_data) => {
if let Some(villager) =
any.downcast_ref::<crate::entity::passive::villager::VillagerEntity>()
{
{
let mut vdata = villager
.villager_data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
vdata.profession = pumpkin_protocol::codec::var_int::VarInt(
from_wit_villager_profession(villager_data.profession) as i32,
);
vdata.level = pumpkin_protocol::codec::var_int::VarInt(i32::from(
villager_data.level,
));
};
villager.xp.store(
villager_data.experience as i32,
std::sync::atomic::Ordering::Relaxed,
);
return Ok(true);
}
}
WitMobData::Creeper(creeper_data) => {
if let Some(creeper) =
any.downcast_ref::<crate::entity::mob::creeper::CreeperEntity>()
{
creeper.set_charged(creeper_data.is_powered);
creeper.set_fuse(creeper_data.fuse);
creeper.set_ignited(creeper_data.is_ignited);
creeper.set_explosion_radius(i32::from(creeper_data.explosion_radius));
return Ok(true);
}
}
WitMobData::Slime(slime_data) => {
if let Some(slime) = any.downcast_ref::<crate::entity::mob::slime::SlimeEntity>() {
slime.set_size(slime_data.size, false);
return Ok(true);
}
}
WitMobData::Enderman(enderman_data) => {
if let Some(enderman) =
any.downcast_ref::<crate::entity::mob::enderman::EndermanEntity>()
{
enderman.set_carried_block(
enderman_data
.carried_block_state
.and_then(pumpkin_data::BlockStateId::new),
);
enderman.set_angry(enderman_data.is_screaming || enderman_data.is_staring);
return Ok(true);
}
}
WitMobData::IronGolem(iron_golem_data) => {
if let Some(iron_golem) =
any.downcast_ref::<crate::entity::passive::iron_golem::IronGolemEntity>()
{
iron_golem.set_player_created(iron_golem_data.is_player_created);
return Ok(true);
}
}
WitMobData::Fox(fox_data) => {
if let Some(fox) = any.downcast_ref::<crate::entity::passive::fox::FoxEntity>() {
fox.set_sitting(fox_data.is_sitting);
fox.set_sleeping(fox_data.is_sleeping);
fox.set_crouching(fox_data.is_crouching);
return Ok(true);
}
}
WitMobData::Shulker(shulker_data) => {
if let Some(shulker) =
any.downcast_ref::<crate::entity::mob::shulker::ShulkerEntity>()
{
shulker.set_attach_face(from_wit_block_direction(shulker_data.attached_face));
shulker.set_raw_peek(shulker_data.peek_amount);
shulker.set_color(shulker_data.color.map(from_wit_dye_color));
return Ok(true);
}
}
WitMobData::Zombie(zombie_data) => {
if let Some(zombie) =
any.downcast_ref::<crate::entity::mob::zombie::zombie::ZombieEntity>()
{
zombie.set_baby(zombie_data.is_baby);
zombie.set_can_break_doors(zombie_data.can_break_doors);
return Ok(true);
}
}
WitMobData::Ageable(ageable_data) => {
if let Some(living) = entity.get_living_entity() {
let age = if ageable_data.is_baby && ageable_data.age >= 0 {
-24000
} else {
ageable_data.age
};
living
.entity
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
return Ok(true);
}
}
WitMobData::Generic => return Ok(true),
}
Ok(false)
}
async fn drop(&mut self, rep: Resource<WitMob>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<MobResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@@ -22,6 +22,8 @@ pub mod common;
#[allow(clippy::unused_async_trait_impl)]
pub mod context;
#[allow(clippy::unused_async_trait_impl)]
pub mod datapack;
#[allow(clippy::unused_async_trait_impl)]
pub mod display;
#[allow(clippy::unused_async_trait_impl)]
pub mod enchantment;
@@ -34,12 +36,18 @@ pub mod generated_packets;
pub mod gui;
#[allow(clippy::unused_async_trait_impl)]
pub mod i18n;
#[allow(clippy::unused_async_trait_impl)]
pub mod inventory;
pub mod ipc;
#[allow(clippy::unused_async_trait_impl)]
pub mod item_stack;
pub mod java_dialogs;
#[allow(clippy::unused_async_trait_impl)]
pub mod living_entity;
#[allow(clippy::unused_async_trait_impl)]
pub mod logging;
#[allow(clippy::unused_async_trait_impl)]
pub mod mob;
pub mod permission;
#[allow(clippy::unused_async_trait_impl)]
pub mod player;

View File

@@ -15,10 +15,10 @@ use crate::{
GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource,
},
wit::v0_1::{
entity::from_wit_damage_type,
events::{
from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position,
},
living_entity::from_wit_damage_type,
pumpkin::{
self,
plugin::damage_types::DamageType as WitDamageType,
@@ -1136,6 +1136,34 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(())
}
async fn get_inventory(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::PlayerInventory,
>,
>{
let player = player_from_resource(self, &player)?;
self.add_player_inventory(player)
}
async fn get_ender_chest(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::inventory::Inventory,
>,
>{
let player = player_from_resource(self, &player)?;
self.add_inventory(
crate::plugin::loader::wasm::wasm_host::state::InventoryProvider::PlayerEnderChest(
player,
),
)
}
async fn get_inventory_item(
&mut self,
player: Resource<Player>,

View File

@@ -19,6 +19,7 @@ use crate::plugin::{
wit::v0_1::pumpkin::{
self,
plugin::{
datapack::DatapackManager as WitDatapackManager,
player::{BanIpOptions, BanPlayerOptions, Player},
server::{
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
@@ -606,6 +607,17 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
Ok(ids)
}
async fn get_datapack_manager(
&mut self,
_rep: Resource<Server>,
) -> wasmtime::Result<Resource<WitDatapackManager>> {
let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
self.add_datapack_manager(server.clone())
}
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
self.resource_table
.delete::<ServerResource>(Resource::new_own(rep.rep()))

View File

@@ -2231,7 +2231,7 @@ impl WasmChunkGenerator {
proto_chunk,
};
futures::executor::block_on(async {
let run = async {
let mut store = self.plugin.store.lock().await;
let Ok(buffer_res) = store.data_mut().add_chunk_buffer(chunk_buffer) else {
return;
@@ -2257,7 +2257,13 @@ impl WasmChunkGenerator {
);
}
}
});
};
if let Ok(handle) = tokio::runtime::Handle::try_current() {
tokio::task::block_in_place(|| {
handle.block_on(run);
});
}
}
}

View File

@@ -1009,8 +1009,8 @@ impl PluginManager {
/// Checks if plugin active
#[must_use]
pub async fn is_plugin_active(&self, name: &str) -> bool {
let plugins = self.plugins.read().await;
pub fn is_plugin_active(&self, name: &str) -> bool {
let plugins = self.plugins.blocking_read();
plugins
.iter()
.any(|p| p.metadata.name == name && p.is_active && p.instance.is_some())
@@ -1018,8 +1018,8 @@ impl PluginManager {
/// Get list of active plugins
#[must_use]
pub async fn active_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.read().await;
pub fn active_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.blocking_read();
plugins
.iter()
.filter(|p| p.is_active && p.instance.is_some())
@@ -1029,15 +1029,15 @@ impl PluginManager {
/// Checks if plugin loaded
#[must_use]
pub async fn is_plugin_loaded(&self, name: &str) -> bool {
let plugins = self.plugins.read().await;
pub fn is_plugin_loaded(&self, name: &str) -> bool {
let plugins = self.plugins.blocking_read();
plugins.iter().any(|p| p.metadata.name == name)
}
/// Get list of loaded plugins
#[must_use]
pub async fn loaded_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.read().await;
pub fn loaded_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.blocking_read();
plugins.iter().map(|p| p.metadata.clone()).collect()
}

View File

@@ -315,16 +315,6 @@ impl Server {
};
let server = Arc::new(server);
let gen_pool = Arc::new(
rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("Gen-Pool-{i}"))
.build()
.unwrap_or_else(|err| {
error!("Failed to build generation thread pool: {err}");
std::process::exit(1);
}),
);
// Fetch / generate keys in background tasks to avoid blocking startup
let server_clone = server.clone();
tokio::spawn(async move {
@@ -400,7 +390,6 @@ impl Server {
let l_info = server.level_info.clone(); // Access from struct
let weak = Arc::downgrade(&server);
let config = Arc::new(server.advanced_config.world.clone());
let pool = gen_pool.clone();
tokio::task::spawn_blocking(move || {
info!(
@@ -409,7 +398,7 @@ impl Server {
.color_named(NamedColor::DarkGreen)
.to_pretty_console()
);
let level = into_level(dim.clone(), &config, path, seed, Some(pool));
let level = into_level(dim.clone(), &config, path, seed);
let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak));
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
level.world_portal.store(Arc::new(Some(portal)));
@@ -508,14 +497,8 @@ impl Server {
let config = Arc::new(server.advanced_config.world.clone());
let seed = server.level_info.load().world_gen_settings.seed;
// TODO: gen_pool should be reused
let level = pumpkin_world::dimension::into_level(
dimension.clone(),
&config,
world_path,
seed,
None,
);
let level =
pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed);
let world: World = World::load(level.clone(), l_info, dimension, registry, weak);
let world = Arc::new(world);
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));

View File

@@ -84,8 +84,8 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
use pumpkin_protocol::java::client::play::{
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
CLightUpdate, CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData,
CBlockUpdate, CDisguisedChatMessage, CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent,
PlayerSpawnData,
};
use pumpkin_protocol::java::client::play::{
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
@@ -511,7 +511,11 @@ impl World {
let mut nbt = NbtCompound::new();
entity.write_nbt(&mut nbt);
let chunk = self.level.get_entity_chunk(current_chunk).await;
chunk.data.lock().await.push(nbt);
chunk
.data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(nbt);
chunk.mark_dirty(true);
}
@@ -3144,20 +3148,23 @@ impl World {
if client.version.load() < JavaMinecraftVersion::V_1_20_2
&& client.version.load() >= JavaMinecraftVersion::V_1_13
{
let mut tags = Vec::new();
let version = client.version.load();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
if let Ok(Ok(packet_data)) = tokio::task::spawn_blocking(move || {
let mut tags = Vec::new();
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
if pumpkin_data::tag::get_registry_key_tags(version, key)
.is_some_and(|map| !map.is_empty())
{
tags.push(key);
}
}
let packet = pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(&tags);
JavaClient::serialize_packet_for_version(&packet, version)
})
.await
{
client.send_packet_now(packet_data).await;
}
client
.send_packet(&pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(
&tags,
))
.await;
}
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
@@ -3205,19 +3212,7 @@ impl World {
return;
}
}
if client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
client.send_packet(&CChunkBatchStart).await;
}
client.send_packet(&CChunkData(&chunk)).await;
if client.version.load() >= JavaMinecraftVersion::V_1_14
&& client.version.load() < JavaMinecraftVersion::V_1_18
&& let Ok(light_packet) = CLightUpdate::from_chunk(&chunk, client.version.load())
{
client.send_packet(&light_packet).await;
}
if client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
client.send_packet(&CChunkBatchEnd::new(1u16)).await;
}
client.send_chunks(&[chunk]).await;
let velocity = player.living_entity.entity.velocity.load();
@@ -4245,20 +4240,7 @@ impl World {
.level
.get_or_fetch_chunk(center_chunk, std::clone::Clone::clone)
.await;
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
java_client.send_packet(&CChunkBatchStart).await;
}
java_client.send_packet(&CChunkData(&chunk)).await;
if java_client.version.load() >= JavaMinecraftVersion::V_1_14
&& java_client.version.load() < JavaMinecraftVersion::V_1_18
&& let Ok(light_packet) =
CLightUpdate::from_chunk(&chunk, java_client.version.load())
{
java_client.send_packet(&light_packet).await;
}
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
java_client.send_packet(&CChunkBatchEnd::new(1u16)).await;
}
java_client.send_chunks(&[chunk]).await;
}
// Send teleport packet after at least the center chunk was delivered
@@ -4360,7 +4342,12 @@ impl World {
// truth, so the chunk's NBT is taken (cleared) to avoid keeping
// a duplicate copy that would be re-appended on the next unload
// and doubled on every reload.
let entity_nbts = std::mem::take(&mut *chunk.data.lock().await);
let entity_nbts = std::mem::take(
&mut *chunk
.data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
let mut entities_to_add: Vec<Arc<dyn EntityBase>> =
Vec::with_capacity(entity_nbts.len());
for entity_nbt in &entity_nbts {