mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: more plugin entity stuff
This commit is contained in:
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -1179,19 +1179,6 @@ version = "0.8.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
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]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -3098,12 +3085,6 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pointers"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5dcdc93847ad24990939cce6e1804361e903efcb5f99daa5abd87943a9d6d7ba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -3464,7 +3445,6 @@ dependencies = [
|
|||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
"rand",
|
"rand",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tokio",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3581,7 +3561,6 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"crossfire",
|
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ thiserror = { version = "2.0", default-features = false }
|
|||||||
bytes = { version = "1.12", default-features = false, features = ["std"] }
|
bytes = { version = "1.12", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
# Concurrency/Parallelism and Synchronization
|
# 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 }
|
rayon = { version = "1.12", default-features = false }
|
||||||
crossbeam = { version = "0.8", default-features = false, features = ["std"] }
|
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"] }
|
crc-fast = { version = "1.10.0", default-features = false, features = ["std"] }
|
||||||
criterion = { version = "0.8", default-features = false }
|
criterion = { version = "0.8", default-features = false }
|
||||||
crossbeam-utils = { version = "0.8.22", default-features = false, features = ["std"] }
|
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 }
|
crypto-bigint = { version = "0.7.5", default-features = false }
|
||||||
dashmap = { version = "6.2", default-features = false }
|
dashmap = { version = "6.2", default-features = false }
|
||||||
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }
|
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ pumpkin-util.workspace = true
|
|||||||
|
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tokio.workspace = true
|
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
31
crates/pumpkin-plugin-api/src/datapack.rs
Normal file
31
crates/pumpkin-plugin-api/src/datapack.rs
Normal 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};
|
||||||
32
crates/pumpkin-plugin-api/src/inventory.rs
Normal file
32
crates/pumpkin-plugin-api/src/inventory.rs
Normal 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};
|
||||||
@@ -76,6 +76,8 @@ use crate::{
|
|||||||
|
|
||||||
/// Plugin command registration and handling utilities.
|
/// Plugin command registration and handling utilities.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
/// Datapack management and query utilities.
|
||||||
|
pub mod datapack;
|
||||||
/// Display and interaction entity utilities and builders.
|
/// Display and interaction entity utilities and builders.
|
||||||
pub mod display;
|
pub mod display;
|
||||||
/// Custom enchantment registration and builder utilities.
|
/// Custom enchantment registration and builder utilities.
|
||||||
@@ -85,6 +87,10 @@ pub mod events;
|
|||||||
mod ext;
|
mod ext;
|
||||||
/// Bedrock UI form builders.
|
/// Bedrock UI form builders.
|
||||||
pub mod forms;
|
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.
|
/// Constants for plugin permissions.
|
||||||
///
|
///
|
||||||
/// Use these in your `PluginMetadata` to request access to specific host features.
|
/// 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,
|
advancement as advancement_wit, bedrock_packets, block_entity, boss_bar,
|
||||||
command as command_wit, common,
|
command as command_wit, common,
|
||||||
context::{self, Context, MarketplaceMetadata, Server},
|
context::{self, Context, MarketplaceMetadata, Server},
|
||||||
damage_types as damage_types_wit, data_components, display as display_wit,
|
damage_types as damage_types_wit, data_components, datapack as datapack_wit,
|
||||||
enchantments as enchantments_wit, entity,
|
display as display_wit, enchantments as enchantments_wit, entity,
|
||||||
entity_types::EntityType,
|
entity_types::EntityType,
|
||||||
event::{self as events_wit, EventType},
|
event::{self as events_wit, EventType},
|
||||||
gui, i18n, ipc, item_stack, java_dialogs, java_packets, particles, permission, player,
|
gui, i18n, inventory as inventory_wit, ipc, item_stack, java_dialogs, java_packets, particles,
|
||||||
recipe as recipe_wit, scoreboard, screens as screens_wit, server, statistics as statistics_wit,
|
permission, player, recipe as recipe_wit, scoreboard, screens as screens_wit, server,
|
||||||
text, uuid, world,
|
statistics as statistics_wit, text, uuid, world,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience re-exports of commonly-used plugin types so plugin authors can
|
// 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`).
|
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
|
||||||
pub use damage_types_wit::DamageType;
|
pub use damage_types_wit::DamageType;
|
||||||
|
pub use datapack::{DatapackInfo, DatapackManager, EnablePosition};
|
||||||
pub use display::{
|
pub use display::{
|
||||||
BillboardMode, BlockDisplayEntity, DisplayEntity, DisplayEntityExt, DisplayTransformation,
|
BillboardMode, BlockDisplayEntity, DisplayEntity, DisplayEntityExt, DisplayTransformation,
|
||||||
EntityDisplayExt, InteractionEntity, ItemDisplayEntity, ItemDisplayEntityExt, ItemDisplayMode,
|
EntityDisplayExt, InteractionEntity, ItemDisplayEntity, ItemDisplayEntityExt, ItemDisplayMode,
|
||||||
@@ -134,6 +141,13 @@ pub use enchantment::{
|
|||||||
};
|
};
|
||||||
pub use events::{EventHandler, FromIntoEvent};
|
pub use events::{EventHandler, FromIntoEvent};
|
||||||
pub use ext::player::PlayerEnderChestExt;
|
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::{
|
pub use recipe::{
|
||||||
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
|
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
|
||||||
RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder,
|
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::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
|
||||||
pub use wit::pumpkin::plugin::server::Dimension;
|
pub use wit::pumpkin::plugin::server::Dimension;
|
||||||
pub use wit::pumpkin::plugin::world::{
|
pub use wit::pumpkin::plugin::world::{
|
||||||
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, RayTraceBlockResult,
|
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, LivingEntity, Mob,
|
||||||
RayTraceEntityResult, RaycastResult, World, WorldBorder,
|
PathNodeType, RayTraceBlockResult, RayTraceEntityResult, RaycastResult, World, WorldBorder,
|
||||||
};
|
};
|
||||||
pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager};
|
pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager};
|
||||||
|
|
||||||
|
|||||||
689
crates/pumpkin-plugin-api/src/mobs.rs
Normal file
689
crates/pumpkin-plugin-api/src/mobs.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Submodule crates/pumpkin-plugin-wit updated: 10f15637dc...a1ba8b3db2
@@ -55,7 +55,6 @@ tokio-util = { workspace = true, features = ["rt"] }
|
|||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
slotmap.workspace = true
|
slotmap.workspace = true
|
||||||
crossfire.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|||||||
@@ -73,11 +73,8 @@ fn bench_chunk_deserialization(c: &mut Criterion) {
|
|||||||
let Chunk::Level(chunk) = chunk else {
|
let Chunk::Level(chunk) = chunk else {
|
||||||
panic!("full generation must return a level chunk");
|
panic!("full generation must return a level chunk");
|
||||||
};
|
};
|
||||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
let bytes = chunk
|
||||||
.build()
|
.to_bytes()
|
||||||
.expect("failed to create benchmark runtime");
|
|
||||||
let bytes = runtime
|
|
||||||
.block_on(chunk.to_bytes())
|
|
||||||
.expect("failed to serialize benchmark chunk");
|
.expect("failed to serialize benchmark chunk");
|
||||||
let position = Vector2::new(chunk.x, chunk.z);
|
let position = Vector2::new(chunk.x, chunk.z);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use std::{
|
|||||||
io::{Read, SeekFrom, Write},
|
io::{Read, SeekFrom, Write},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
pin::Pin,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
@@ -308,7 +307,7 @@ impl AnvilChunkData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_chunk<S>(
|
fn from_chunk<S>(
|
||||||
chunk: &S,
|
chunk: &S,
|
||||||
compression: Option<Compression>,
|
compression: Option<Compression>,
|
||||||
chunk_config: &AnvilChunkConfig,
|
chunk_config: &AnvilChunkConfig,
|
||||||
@@ -318,7 +317,6 @@ impl AnvilChunkData {
|
|||||||
{
|
{
|
||||||
let raw_bytes = chunk
|
let raw_bytes = chunk
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.await
|
|
||||||
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
|
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
|
||||||
|
|
||||||
let compression = compression.unwrap_or_else(|| chunk_config.compression.algorithm.into());
|
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 {
|
pub trait SingleChunkDataSerializer: Send + Sync + Sized + Dirtiable + 'static {
|
||||||
fn to_bytes(
|
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError>;
|
||||||
&self,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>;
|
|
||||||
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError>;
|
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError>;
|
||||||
fn position(&self) -> (i32, i32);
|
fn position(&self) -> (i32, i32);
|
||||||
}
|
}
|
||||||
@@ -620,8 +616,7 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
|
|||||||
let compression_type = self.chunks_data[index]
|
let compression_type = self.chunks_data[index]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|chunk_data| chunk_data.serialized_data.compression);
|
.and_then(|chunk_data| chunk_data.serialized_data.compression);
|
||||||
let new_chunk_data =
|
let new_chunk_data = AnvilChunkData::from_chunk(chunk, compression_type, chunk_config)?;
|
||||||
AnvilChunkData::from_chunk(chunk, compression_type, chunk_config).await?;
|
|
||||||
|
|
||||||
let mut write_action = self.write_action.lock().await;
|
let mut write_action = self.write_action.lock().await;
|
||||||
if !chunk_config.write_in_place {
|
if !chunk_config.write_in_place {
|
||||||
|
|||||||
@@ -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 index = Self::get_chunk_index(chunk.position().0, chunk.position().1);
|
||||||
let chunk_raw: Bytes = chunk
|
let chunk_raw: Bytes = chunk
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.await
|
|
||||||
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
|
.map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?;
|
||||||
|
|
||||||
self.timestamps[index] = SystemTime::now()
|
self.timestamps[index] = SystemTime::now()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
pin::Pin,
|
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
RwLock,
|
RwLock,
|
||||||
@@ -13,7 +12,6 @@ use pumpkin_data::{Block, BlockStateId, chunk::ChunkStatus, fluid::Fluid};
|
|||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation};
|
use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk::{
|
chunk::{
|
||||||
@@ -43,10 +41,8 @@ impl SingleChunkDataSerializer for ChunkData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_bytes(
|
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||||
&self,
|
Ok(self.internal_to_bytes())
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
|
|
||||||
Box::pin(async move { Ok(self.internal_to_bytes()) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -707,10 +703,8 @@ impl SingleChunkDataSerializer for ChunkEntityData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_bytes(
|
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||||
&self,
|
Ok(self.internal_to_bytes())
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
|
|
||||||
Box::pin(async move { self.internal_to_bytes().await })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -775,12 +769,12 @@ impl ChunkEntityData {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
x: position.x,
|
x: position.x,
|
||||||
z: position.y,
|
z: position.y,
|
||||||
data: Mutex::new(entities),
|
data: std::sync::Mutex::new(entities),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
fn internal_to_bytes(&self) -> Bytes {
|
||||||
let mut root = NbtCompound::new();
|
let mut root = NbtCompound::new();
|
||||||
root.put_int("DataVersion", WORLD_DATA_VERSION);
|
root.put_int("DataVersion", WORLD_DATA_VERSION);
|
||||||
root.put(
|
root.put(
|
||||||
@@ -790,14 +784,14 @@ impl ChunkEntityData {
|
|||||||
let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self
|
let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self
|
||||||
.data
|
.data
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone()))
|
.map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
root.put_list("Entities", entities_tag);
|
root.put_list("Entities", entities_tag);
|
||||||
|
|
||||||
let nbt = pumpkin_nbt::Nbt::from(root);
|
let nbt = pumpkin_nbt::Nbt::from(root);
|
||||||
Ok(nbt.write())
|
nbt.write()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ where
|
|||||||
|
|
||||||
let bytes = chunk_data
|
let bytes = chunk_data
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.await
|
|
||||||
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
|
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
|
||||||
|
|
||||||
let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest);
|
let compressed = compress_to_vec(&bytes[..], CompressionLevel::Fastest);
|
||||||
@@ -165,9 +164,6 @@ mod tests {
|
|||||||
use crate::chunk::io::{ChunkSerializer, LoadedData};
|
use crate::chunk::io::{ChunkSerializer, LoadedData};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@@ -185,17 +181,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SingleChunkDataSerializer for MockChunk {
|
impl SingleChunkDataSerializer for MockChunk {
|
||||||
fn to_bytes(
|
fn to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||||
&self,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>
|
|
||||||
{
|
|
||||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||||
root.put_int("x", self.x);
|
root.put_int("x", self.x);
|
||||||
root.put_int("z", self.z);
|
root.put_int("z", self.z);
|
||||||
let i8_vec: Vec<i8> = self.data.iter().map(|&b| b as i8).collect();
|
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()));
|
root.put("data", pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into()));
|
||||||
let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed();
|
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> {
|
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError> {
|
||||||
let mut cursor = std::io::Cursor::new(bytes);
|
let mut cursor = std::io::Cursor::new(bytes);
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ use tokio::{
|
|||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk::{
|
chunk::{ChunkReadingError, ChunkWritingError, io::Dirtiable},
|
||||||
ChunkReadingError, ChunkWritingError,
|
|
||||||
io::{BoxFuture, Dirtiable},
|
|
||||||
},
|
|
||||||
level::LevelFolder,
|
level::LevelFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -201,230 +198,209 @@ where
|
|||||||
{
|
{
|
||||||
type Data = Arc<S::Data>;
|
type Data = Arc<S::Data>;
|
||||||
|
|
||||||
fn watch_chunks<'a>(
|
async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
|
||||||
&'a self,
|
let paths: Vec<_> = chunks
|
||||||
folder: &'a LevelFolder,
|
.iter()
|
||||||
chunks: &'a [Vector2<i32>],
|
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
|
||||||
) -> BoxFuture<'a, ()> {
|
.collect();
|
||||||
Box::pin(async move {
|
|
||||||
let paths: Vec<_> = chunks
|
|
||||||
.iter()
|
|
||||||
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut watchers = self.watchers.write().await;
|
let mut watchers = self.watchers.write().await;
|
||||||
for path in paths {
|
for path in paths {
|
||||||
*watchers.entry(path).or_insert(0) += 1;
|
*watchers.entry(path).or_insert(0) += 1;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch_chunks<'a>(
|
async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
|
||||||
&'a self,
|
let paths: Vec<_> = chunks
|
||||||
folder: &'a LevelFolder,
|
.iter()
|
||||||
chunks: &'a [Vector2<i32>],
|
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
|
||||||
) -> BoxFuture<'a, ()> {
|
.collect();
|
||||||
Box::pin(async move {
|
|
||||||
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 paths_to_evict = Vec::new();
|
||||||
{
|
{
|
||||||
let mut watchers = self.watchers.write().await;
|
let mut watchers = self.watchers.write().await;
|
||||||
for path in paths {
|
for path in paths {
|
||||||
if let std::collections::btree_map::Entry::Occupied(mut e) =
|
if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) {
|
||||||
watchers.entry(path)
|
let count = e.get_mut();
|
||||||
{
|
*count = count.saturating_sub(1);
|
||||||
let count = e.get_mut();
|
if *count == 0 {
|
||||||
*count = count.saturating_sub(1);
|
let (path, _) = e.remove_entry();
|
||||||
if *count == 0 {
|
paths_to_evict.push(path);
|
||||||
let (path, _) = e.remove_entry();
|
|
||||||
paths_to_evict.push(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for path in paths_to_evict {
|
for path in paths_to_evict {
|
||||||
self.maybe_evict(&path).await;
|
self.maybe_evict(&path).await;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
|
async fn clear_watched_chunks(&self) {
|
||||||
Box::pin(async move {
|
let paths: Vec<PathBuf> = {
|
||||||
let paths: Vec<PathBuf> = {
|
let mut watchers = self.watchers.write().await;
|
||||||
let mut watchers = self.watchers.write().await;
|
let keys: Vec<_> = watchers.keys().cloned().collect();
|
||||||
let keys: Vec<_> = watchers.keys().cloned().collect();
|
watchers.clear();
|
||||||
watchers.clear();
|
keys
|
||||||
keys
|
};
|
||||||
};
|
for path in paths {
|
||||||
for path in paths {
|
self.maybe_evict(&path).await;
|
||||||
self.maybe_evict(&path).await;
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_chunks<'a>(
|
async fn fetch_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunk_coords: &'a [Vector2<i32>],
|
chunk_coords: &'a [Vector2<i32>],
|
||||||
stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
||||||
) -> BoxFuture<'a, ()> {
|
) {
|
||||||
Box::pin(async move {
|
// Group requested chunk coords by their region file.
|
||||||
// Group requested chunk coords by their region file.
|
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
|
||||||
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
|
for at in chunk_coords {
|
||||||
for at in chunk_coords {
|
regions_chunks
|
||||||
regions_chunks
|
.entry(S::get_chunk_key(at))
|
||||||
.entry(S::get_chunk_key(at))
|
.or_default()
|
||||||
.or_default()
|
.push(*at);
|
||||||
.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)| {
|
join_all(region_tasks).await;
|
||||||
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;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_chunks<'a>(
|
async fn save_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
|
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
|
||||||
) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
|
) -> Result<(), ChunkWritingError> {
|
||||||
Box::pin(async move {
|
// Group chunks by region file.
|
||||||
// Group chunks by region file.
|
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
|
||||||
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
|
for (at, chunk) in chunks_data {
|
||||||
for (at, chunk) in chunks_data {
|
regions_chunks
|
||||||
regions_chunks
|
.entry(S::get_chunk_key(&at))
|
||||||
.entry(S::get_chunk_key(&at))
|
.or_default()
|
||||||
.or_default()
|
.push(chunk);
|
||||||
.push(chunk);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let tasks = regions_chunks
|
let tasks = regions_chunks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(file_name, chunk_locks)| async move {
|
.map(|(file_name, chunk_locks)| async move {
|
||||||
let path = P::file_path(folder, &file_name);
|
let path = P::file_path(folder, &file_name);
|
||||||
trace!("Saving chunks into {}", path.display());
|
trace!("Saving chunks into {}", path.display());
|
||||||
|
|
||||||
let chunk_serializer = match self.get_serializer(&path).await {
|
let chunk_serializer = match self.get_serializer(&path).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(ChunkReadingError::ChunkNotExist) => {
|
Err(ChunkReadingError::ChunkNotExist) => {
|
||||||
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
||||||
"get_serializer returned ChunkNotExist",
|
"get_serializer returned ChunkNotExist",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Err(ChunkReadingError::IoError(err)) => {
|
Err(ChunkReadingError::IoError(err)) => {
|
||||||
error!("I/O error reading region before write: {err}");
|
error!("I/O error reading region before write: {err}");
|
||||||
return Err(ChunkWritingError::IoError(err));
|
return Err(ChunkWritingError::IoError(err));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
||||||
err.to_string(),
|
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;
|
let serializer = chunk_serializer.read().await;
|
||||||
for chunk in &chunk_locks {
|
debug!("Flushing {} to disk", path.display());
|
||||||
// Atomically snapshot and clear the dirty flag before we
|
serializer
|
||||||
// write so that any mutation that races in *during* this
|
.write(&path)
|
||||||
// serialisation round will mark dirty again correctly.
|
.await
|
||||||
let was_dirty = chunk.is_dirty();
|
.map_err(ChunkWritingError::IoError)?;
|
||||||
chunk.mark_dirty(false);
|
// Read-lock released here.
|
||||||
|
|
||||||
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 {
|
// Drop our handle so `can_remove` may succeed.
|
||||||
// A read-lock suffices for `write()` since we have already
|
drop(chunk_serializer);
|
||||||
// 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.
|
// Evict the cache entry when no longer needed.
|
||||||
drop(chunk_serializer);
|
self.maybe_evict(&path).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Evict the cache entry when no longer needed.
|
Ok(())
|
||||||
self.maybe_evict(&path).await;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
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
|
/// 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
|
/// This is a linearisation point: after this future resolves no mutation
|
||||||
/// started before the call is still running.
|
/// started before the call is still running.
|
||||||
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
|
async fn block_and_await_ongoing_tasks(&self) {
|
||||||
Box::pin(async move {
|
// Snapshot the current set of loaders under a read-lock so we do
|
||||||
// Snapshot the current set of loaders under a read-lock so we do
|
// not block new insertions longer than necessary.
|
||||||
// not block new insertions longer than necessary.
|
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
|
||||||
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
|
{ self.file_locks.read().await.values().cloned().collect() };
|
||||||
{ self.file_locks.read().await.values().cloned().collect() };
|
|
||||||
|
|
||||||
// For each loader that has been initialised, acquire a write-lock
|
// For each loader that has been initialised, acquire a write-lock
|
||||||
// and release it immediately. This guarantees that any concurrent
|
// and release it immediately. This guarantees that any concurrent
|
||||||
// read or write operation that was in progress has finished.
|
// read or write operation that was in progress has finished.
|
||||||
let drain_tasks = loaders.into_iter().map(|loader| async move {
|
let drain_tasks = loaders.into_iter().map(|loader| async move {
|
||||||
if let Some(serializer_arc) = loader.internal.get() {
|
if let Some(serializer_arc) = loader.internal.get() {
|
||||||
// Acquiring + immediately dropping the write-lock acts as a
|
// Acquiring + immediately dropping the write-lock acts as a
|
||||||
// barrier: it can only succeed once all current lock holders
|
// barrier: it can only succeed once all current lock holders
|
||||||
// have released their guards.
|
// have released their guards.
|
||||||
let _guard = serializer_arc.write().await;
|
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>;
|
type Data = Arc<P>;
|
||||||
|
|
||||||
fn fetch_chunks<'a>(
|
async fn fetch_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunk_coords: &'a [Vector2<i32>],
|
chunk_coords: &'a [Vector2<i32>],
|
||||||
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
||||||
) -> BoxFuture<'a, ()> {
|
) {
|
||||||
match self {
|
match self {
|
||||||
Self::Linear(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),
|
Self::Anvil(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
|
||||||
Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream),
|
Self::Pump(io) => io.fetch_chunks(folder, chunk_coords, stream).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_chunks<'a>(
|
async fn save_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
|
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
|
||||||
) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
|
) -> Result<(), ChunkWritingError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Linear(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),
|
Self::Anvil(io) => io.save_chunks(folder, chunks_data).await,
|
||||||
Self::Pump(io) => io.save_chunks(folder, chunks_data),
|
Self::Pump(io) => io.save_chunks(folder, chunks_data).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch_chunks<'a>(
|
async fn watch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
|
||||||
&'a self,
|
|
||||||
folder: &'a LevelFolder,
|
|
||||||
chunks: &'a [Vector2<i32>],
|
|
||||||
) -> BoxFuture<'a, ()> {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Linear(io) => io.watch_chunks(folder, chunks),
|
Self::Linear(io) => io.watch_chunks(folder, chunks).await,
|
||||||
Self::Anvil(io) => io.watch_chunks(folder, chunks),
|
Self::Anvil(io) => io.watch_chunks(folder, chunks).await,
|
||||||
Self::Pump(io) => io.watch_chunks(folder, chunks),
|
Self::Pump(io) => io.watch_chunks(folder, chunks).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch_chunks<'a>(
|
async fn unwatch_chunks<'a>(&'a self, folder: &'a LevelFolder, chunks: &'a [Vector2<i32>]) {
|
||||||
&'a self,
|
|
||||||
folder: &'a LevelFolder,
|
|
||||||
chunks: &'a [Vector2<i32>],
|
|
||||||
) -> BoxFuture<'a, ()> {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Linear(io) => io.unwatch_chunks(folder, chunks),
|
Self::Linear(io) => io.unwatch_chunks(folder, chunks).await,
|
||||||
Self::Anvil(io) => io.unwatch_chunks(folder, chunks),
|
Self::Anvil(io) => io.unwatch_chunks(folder, chunks).await,
|
||||||
Self::Pump(io) => io.unwatch_chunks(folder, chunks),
|
Self::Pump(io) => io.unwatch_chunks(folder, chunks).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
|
async fn clear_watched_chunks(&self) {
|
||||||
match self {
|
match self {
|
||||||
Self::Linear(io) => io.clear_watched_chunks(),
|
Self::Linear(io) => io.clear_watched_chunks().await,
|
||||||
Self::Anvil(io) => io.clear_watched_chunks(),
|
Self::Anvil(io) => io.clear_watched_chunks().await,
|
||||||
Self::Pump(io) => io.clear_watched_chunks(),
|
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 {
|
match self {
|
||||||
Self::Linear(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(),
|
Self::Anvil(io) => io.block_and_await_ongoing_tasks().await,
|
||||||
Self::Pump(io) => io.block_and_await_ongoing_tasks(),
|
Self::Pump(io) => io.block_and_await_ongoing_tasks().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{error, pin::Pin};
|
use std::error;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
@@ -38,8 +38,6 @@ pub trait Dirtiable {
|
|||||||
fn mark_dirty(&self, flag: bool);
|
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
|
/// Trait to handle the IO of chunks
|
||||||
/// for loading and saving chunks data
|
/// for loading and saving chunks data
|
||||||
/// can be implemented for different types of IO
|
/// can be implemented for different types of IO
|
||||||
@@ -59,34 +57,34 @@ where
|
|||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunk_coords: &'a [Vector2<i32>],
|
chunk_coords: &'a [Vector2<i32>],
|
||||||
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
||||||
) -> BoxFuture<'a, ()>; // Returns BoxFuture<()>
|
) -> impl Future<Output = ()> + Send + 'a;
|
||||||
|
|
||||||
/// Persist the chunks data
|
/// Persist the chunks data
|
||||||
fn save_chunks<'a>(
|
fn save_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunks_data: Vec<(Vector2<i32>, Self::Data)>,
|
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
|
/// Tells the `ChunkIO` that these chunks are currently loaded in memory
|
||||||
fn watch_chunks<'a>(
|
fn watch_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunks: &'a [Vector2<i32>],
|
chunks: &'a [Vector2<i32>],
|
||||||
) -> BoxFuture<'a, ()>;
|
) -> impl Future<Output = ()> + Send + 'a;
|
||||||
|
|
||||||
/// Tells the `ChunkIO` that these chunks are no longer loaded in memory
|
/// Tells the `ChunkIO` that these chunks are no longer loaded in memory
|
||||||
fn unwatch_chunks<'a>(
|
fn unwatch_chunks<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
folder: &'a LevelFolder,
|
folder: &'a LevelFolder,
|
||||||
chunks: &'a [Vector2<i32>],
|
chunks: &'a [Vector2<i32>],
|
||||||
) -> BoxFuture<'a, ()>;
|
) -> impl Future<Output = ()> + Send + 'a;
|
||||||
|
|
||||||
/// Tells the `ChunkIO` that no more chunks are loaded in memory
|
/// 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
|
/// 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.
|
/// Trait to serialize and deserialize the chunk data to and from bytes.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use std::sync::RwLock;
|
|||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
pub mod format;
|
pub mod format;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
@@ -90,7 +89,7 @@ pub struct ChunkEntityData {
|
|||||||
pub x: i32,
|
pub x: i32,
|
||||||
/// Chunk Z
|
/// Chunk Z
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
pub data: Mutex<Vec<NbtCompound>>,
|
pub data: std::sync::Mutex<Vec<NbtCompound>>,
|
||||||
|
|
||||||
pub dirty: AtomicBool,
|
pub dirty: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use super::chunk_holder::ChunkHolder;
|
|||||||
use super::chunk_state::{Chunk, StagedChunkEnum};
|
use super::chunk_state::{Chunk, StagedChunkEnum};
|
||||||
use super::dag::{DAG, EdgeKey, Node, NodeKey};
|
use super::dag::{DAG, EdgeKey, Node, NodeKey};
|
||||||
use super::generation_cache::Cache;
|
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::{
|
use super::{
|
||||||
ChunkLevel, ChunkListener, ChunkLoading, ChunkPos, HashMapType, HashSetType, IOLock,
|
ChunkLevel, ChunkListener, ChunkLoading, ChunkPos, HashMapType, HashSetType, IOLock,
|
||||||
LevelChannel,
|
LevelChannel,
|
||||||
@@ -69,12 +69,10 @@ pub struct GenerationSchedule {
|
|||||||
running_task_count: u16,
|
running_task_count: u16,
|
||||||
max_in_flight: u16,
|
max_in_flight: u16,
|
||||||
queue_dirty: bool,
|
queue_dirty: bool,
|
||||||
recv_chunk: crossfire::compat::MRx<(ChunkPos, RecvChunk)>,
|
recv_chunk: crossbeam::channel::Receiver<(ChunkPos, RecvChunk)>,
|
||||||
io_read: crossfire::compat::MTx<Vec<ChunkPos>>,
|
io_read: tokio::sync::mpsc::Sender<Vec<ChunkPos>>,
|
||||||
io_write: crossfire::compat::Tx<Vec<(ChunkPos, Chunk)>>,
|
io_write: tokio::sync::mpsc::Sender<Vec<(ChunkPos, Chunk)>>,
|
||||||
generate: crossfire::compat::MTx<(ChunkPos, Cache, StagedChunkEnum)>,
|
send_chunk: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
|
||||||
send_chunk: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
|
|
||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
|
||||||
listener: Arc<ChunkListener>,
|
listener: Arc<ChunkListener>,
|
||||||
lighting_config: LightingEngineConfig,
|
lighting_config: LightingEngineConfig,
|
||||||
last_unload: std::time::Instant,
|
last_unload: std::time::Instant,
|
||||||
@@ -83,22 +81,17 @@ pub struct GenerationSchedule {
|
|||||||
impl GenerationSchedule {
|
impl GenerationSchedule {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
io_read_thread_count: usize,
|
io_read_thread_count: usize,
|
||||||
gen_thread_count: usize,
|
|
||||||
level: Arc<Level>,
|
level: Arc<Level>,
|
||||||
level_channel: Arc<LevelChannel>,
|
level_channel: Arc<LevelChannel>,
|
||||||
listener: Arc<ChunkListener>,
|
listener: Arc<ChunkListener>,
|
||||||
thread_tracker: &mut Vec<thread::JoinHandle<()>>,
|
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) =
|
let (send_read_io, recv_read_io) = tokio::sync::mpsc::channel(io_read_thread_count + 5);
|
||||||
crossfire::compat::mpmc::bounded_tx_blocking_rx_async(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) =
|
let (send_write_io, recv_write_io) = tokio::sync::mpsc::channel(500);
|
||||||
crossfire::compat::spsc::bounded_tx_blocking_rx_async(500);
|
|
||||||
|
|
||||||
let (send_gen, recv_gen) = crossfire::compat::mpmc::bounded_blocking(gen_thread_count + 5);
|
|
||||||
|
|
||||||
let io_lock = Arc::new((
|
let io_lock = Arc::new((
|
||||||
Mutex::new(HashMapType::default()),
|
Mutex::new(HashMapType::default()),
|
||||||
@@ -120,28 +113,8 @@ impl GenerationSchedule {
|
|||||||
io_lock.clone(),
|
io_lock.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
if gen_pool.is_none() {
|
let max_in_flight =
|
||||||
for i in 0..gen_thread_count {
|
(thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16;
|
||||||
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 level_sched = level;
|
let level_sched = level;
|
||||||
let lighting_config = level_sched.lighting_config;
|
let lighting_config = level_sched.lighting_config;
|
||||||
@@ -164,9 +137,7 @@ impl GenerationSchedule {
|
|||||||
recv_chunk,
|
recv_chunk,
|
||||||
io_read: send_read_io,
|
io_read: send_read_io,
|
||||||
io_write: send_write_io,
|
io_write: send_write_io,
|
||||||
generate: send_gen,
|
|
||||||
send_chunk,
|
send_chunk,
|
||||||
gen_pool,
|
|
||||||
listener,
|
listener,
|
||||||
chunk_map: HashMap::default(),
|
chunk_map: HashMap::default(),
|
||||||
lighting_config,
|
lighting_config,
|
||||||
@@ -446,8 +417,12 @@ impl GenerationSchedule {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
if all_ready {
|
if all_ready {
|
||||||
now_ready.push(node_key);
|
if node.in_degree == 0 {
|
||||||
false
|
now_ready.push(node_key);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -463,7 +438,6 @@ impl GenerationSchedule {
|
|||||||
Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage);
|
Self::calc_priority(&self.last_level, &self.last_high_priority, n.pos, n.stage);
|
||||||
self.queue.push(TaskHeapNode(priority, node_key));
|
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;
|
*data.entry(*pos).or_insert(0) += 1;
|
||||||
}
|
}
|
||||||
drop(data);
|
drop(data);
|
||||||
if let Err(e) = self.io_write.send(chunks) {
|
if let Err(e) = self.io_write.blocking_send(chunks) {
|
||||||
error!(
|
error!(
|
||||||
"Failed to send chunks to io write thread during save (may have shut down): {:?}",
|
"Failed to send chunks to io write thread during save (may have shut down): {:?}",
|
||||||
e
|
e
|
||||||
@@ -851,7 +825,7 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
drop(data);
|
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);
|
error!("Failed to send chunks to io write thread: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1311,7 +1285,10 @@ impl GenerationSchedule {
|
|||||||
|
|
||||||
io_batch.push(node.pos);
|
io_batch.push(node.pos);
|
||||||
if io_batch.len() >= 16
|
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...");
|
info!("IO read thread closed, saving remaining chunks...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
@@ -1320,7 +1297,10 @@ impl GenerationSchedule {
|
|||||||
} else {
|
} else {
|
||||||
// Send any pending IO batch before starting generation
|
// Send any pending IO batch before starting generation
|
||||||
if !io_batch.is_empty()
|
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...");
|
info!("IO read thread closed, saving remaining chunks...");
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
@@ -1432,41 +1412,37 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.running_task_count += 1;
|
self.running_task_count += 1;
|
||||||
if let Some(pool) = &self.gen_pool {
|
let pos = node.pos;
|
||||||
let pos = node.pos;
|
let stage = node.stage;
|
||||||
let stage = node.stage;
|
let send_chunk = self.send_chunk.clone();
|
||||||
let send_chunk = self.send_chunk.clone();
|
let level = level.clone();
|
||||||
let level = level.clone();
|
let settings =
|
||||||
let settings = GenerationSettings::from_dimension(
|
GenerationSettings::from_dimension(level.world_gen.load().dimension());
|
||||||
level.world_gen.load().dimension(),
|
|
||||||
);
|
|
||||||
|
|
||||||
pool.spawn(move || {
|
rayon::spawn(move || {
|
||||||
let result = crate::chunk_system::worker_logic::run_generation(
|
let result = crate::chunk_system::worker_logic::run_generation(
|
||||||
pos, cache, stage, &level, settings,
|
pos, cache, stage, &level, settings,
|
||||||
);
|
);
|
||||||
let _ = send_chunk.send((pos, result));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush any remaining IO batch
|
// 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...");
|
info!("IO read thread closed, saving remaining chunks...");
|
||||||
self.save_all_chunk(true);
|
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 self.queue.is_empty() {
|
||||||
// If we have tasks in flight, wait for them with timeout
|
if self.running_task_count > 0 {
|
||||||
if self.running_task_count > 0 || !self.waiting_for_chunks.is_empty() {
|
|
||||||
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
|
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
|
||||||
Ok((pos, data)) => {
|
Ok((pos, data)) => {
|
||||||
self.receive_chunk(pos, data);
|
self.receive_chunk(pos, data);
|
||||||
@@ -1474,16 +1450,17 @@ impl GenerationSchedule {
|
|||||||
self.garbage_collect_dependencies();
|
self.garbage_collect_dependencies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(crossfire::compat::RecvTimeoutError::Timeout) => {
|
Err(crossbeam::channel::RecvTimeoutError::Timeout) => {
|
||||||
// Periodically check LevelChannel for new requests
|
// Periodically check LevelChannel for new requests
|
||||||
if self.resort_work(self.send_level.get()) {
|
if self.resort_work(self.send_level.get()) {
|
||||||
self.garbage_collect_dependencies();
|
self.garbage_collect_dependencies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(crossfire::compat::RecvTimeoutError::Disconnected) => break,
|
Err(crossbeam::channel::RecvTimeoutError::Disconnected) => break,
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
let restored = Self::restore_ready_tasks(
|
||||||
&mut self.graph,
|
&mut self.graph,
|
||||||
&mut self.queue,
|
&mut self.queue,
|
||||||
@@ -1492,10 +1469,14 @@ impl GenerationSchedule {
|
|||||||
&self.last_high_priority,
|
&self.last_high_priority,
|
||||||
&self.waiting_for_chunks,
|
&self.waiting_for_chunks,
|
||||||
);
|
);
|
||||||
if restored > 0 {
|
if restored > 0 || !self.queue.is_empty() {
|
||||||
debug!(
|
debug!(
|
||||||
"Restored {restored} stranded ready chunk tasks to generation queue"
|
"Restored {restored} stranded ready chunk tasks to generation queue"
|
||||||
);
|
);
|
||||||
|
if self.queue_dirty {
|
||||||
|
self.sort_queue();
|
||||||
|
self.queue_dirty = false;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
debug_assert!(self.debug_check());
|
debug_assert!(self.debug_check());
|
||||||
@@ -1508,6 +1489,27 @@ impl GenerationSchedule {
|
|||||||
self.sort_queue();
|
self.sort_queue();
|
||||||
self.queue_dirty = false;
|
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!(
|
info!(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::chunk::format::LightContainer;
|
|||||||
use crate::chunk::io::LoadedData::Loaded;
|
use crate::chunk::io::LoadedData::Loaded;
|
||||||
use crate::chunk::io::{FileIO, LoadedData};
|
use crate::chunk::io::{FileIO, LoadedData};
|
||||||
use crate::level::Level;
|
use crate::level::Level;
|
||||||
use crossfire::compat::AsyncRx;
|
|
||||||
use pumpkin_config::lighting::LightingEngineConfig;
|
use pumpkin_config::lighting::LightingEngineConfig;
|
||||||
use pumpkin_data::chunk::ChunkStatus;
|
use pumpkin_data::chunk::ChunkStatus;
|
||||||
use pumpkin_data::chunk_gen_settings::GenerationSettings;
|
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 {
|
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 (tx, rx) = tokio::sync::oneshot::channel();
|
let world_gen = level.world_gen.load();
|
||||||
let world_gen = level.world_gen.load();
|
let chunk_clone = chunk.clone();
|
||||||
let chunk_clone = chunk.clone();
|
rayon::spawn(move || {
|
||||||
pool.spawn(move || {
|
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
|
||||||
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
|
let _ = tx.send(p);
|
||||||
let _ = tx.send(p);
|
});
|
||||||
});
|
rx.await
|
||||||
rx.await
|
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
|
||||||
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
|
|
||||||
} else {
|
|
||||||
ProtoChunk::from_chunk_data(chunk, &level.world_gen.load())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk {
|
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)
|
proto.light.block_light = (0..section_count)
|
||||||
.map(|_| LightContainer::new_empty(0))
|
.map(|_| LightContainer::new_empty(0))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Set stage to Features
|
|
||||||
proto.stage = StagedChunkEnum::Features;
|
proto.stage = StagedChunkEnum::Features;
|
||||||
|
|
||||||
Chunk::Proto(Box::new(proto))
|
Chunk::Proto(Box::new(proto))
|
||||||
} else {
|
} else {
|
||||||
Chunk::Level(chunk)
|
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(
|
pub async fn io_read_work(
|
||||||
recv: crossfire::compat::MAsyncRx<Vec<ChunkPos>>,
|
recv: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Vec<ChunkPos>>>>,
|
||||||
send: crossfire::compat::MTx<(ChunkPos, RecvChunk)>,
|
send: crossbeam::channel::Sender<(ChunkPos, RecvChunk)>,
|
||||||
level: Arc<Level>,
|
level: Arc<Level>,
|
||||||
lock: IOLock,
|
lock: IOLock,
|
||||||
) {
|
) {
|
||||||
debug!("io read thread start");
|
debug!("io read thread start");
|
||||||
|
|
||||||
// Cleaner loop and async recv
|
// 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 {
|
for pos in &batch {
|
||||||
// Lock handling
|
// Lock handling
|
||||||
loop {
|
loop {
|
||||||
@@ -178,10 +177,14 @@ pub async fn io_read_work(
|
|||||||
debug!("io read thread stop");
|
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 {
|
loop {
|
||||||
// Don't check cancel_token here (keep saving chunks)
|
// 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());
|
// debug!("io write thread receive chunks size {}", data.len());
|
||||||
let mut vec = Vec::with_capacity(data.len());
|
let mut vec = Vec::with_capacity(data.len());
|
||||||
let mut positions = 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ pub fn into_level(
|
|||||||
level_config: &LevelConfig,
|
level_config: &LevelConfig,
|
||||||
base_directory: PathBuf,
|
base_directory: PathBuf,
|
||||||
seed: i64,
|
seed: i64,
|
||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
|
||||||
) -> Arc<Level> {
|
) -> Arc<Level> {
|
||||||
Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool)
|
Level::from_root_folder(level_config, base_directory, seed, dimension)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ pub struct Level {
|
|||||||
pub level_channel: Arc<LevelChannel>,
|
pub level_channel: Arc<LevelChannel>,
|
||||||
pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>,
|
pub thread_tracker: Mutex<Vec<thread::JoinHandle<()>>>,
|
||||||
pub chunk_listener: Arc<ChunkListener>,
|
pub chunk_listener: Arc<ChunkListener>,
|
||||||
pub gen_pool: Option<Arc<rayon::ThreadPool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TickData {
|
pub struct TickData {
|
||||||
@@ -146,7 +145,6 @@ impl Level {
|
|||||||
root_folder: PathBuf,
|
root_folder: PathBuf,
|
||||||
seed: i64,
|
seed: i64,
|
||||||
dimension: Dimension,
|
dimension: Dimension,
|
||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
|
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
|
||||||
Some((ns, n)) => (ns, n),
|
Some((ns, n)) => (ns, n),
|
||||||
@@ -281,19 +279,10 @@ impl Level {
|
|||||||
level_channel: level_channel.clone(),
|
level_channel: level_channel.clone(),
|
||||||
thread_tracker,
|
thread_tracker,
|
||||||
chunk_listener: listener.clone(),
|
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(
|
GenerationSchedule::create(
|
||||||
4,
|
4,
|
||||||
threads_per_dimension,
|
|
||||||
level_ref.clone(),
|
level_ref.clone(),
|
||||||
level_channel,
|
level_channel,
|
||||||
listener,
|
listener,
|
||||||
@@ -302,7 +291,6 @@ impl Level {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.as_mut(),
|
.as_mut(),
|
||||||
gen_pool,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
level_ref
|
level_ref
|
||||||
@@ -319,48 +307,22 @@ impl Level {
|
|||||||
|
|
||||||
pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) {
|
pub fn spawn_entity_generation(self: &Arc<Self>, pos: Vector2<i32>) {
|
||||||
let level = self.clone();
|
let level = self.clone();
|
||||||
if let Some(pool) = &self.gen_pool {
|
rayon::spawn(move || {
|
||||||
pool.spawn(move || {
|
let arc_chunk = Arc::new(ChunkEntityData {
|
||||||
let arc_chunk = Arc::new(ChunkEntityData {
|
x: pos.x,
|
||||||
x: pos.x,
|
z: pos.y,
|
||||||
z: pos.y,
|
data: std::sync::Mutex::new(Vec::new()),
|
||||||
data: tokio::sync::Mutex::new(Vec::new()),
|
dirty: AtomicBool::new(false),
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} 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
|
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
|
||||||
.loaded_entity_chunks
|
|
||||||
.insert(pos, arc_chunk.clone());
|
|
||||||
|
|
||||||
if let Some((_, waiters)) = level_clone.pending_entity_generations.remove(&pos)
|
if let Some((_, waiters)) = level.pending_entity_generations.remove(&pos) {
|
||||||
{
|
for tx in waiters {
|
||||||
for tx in waiters {
|
let _ = tx.send(arc_chunk.clone());
|
||||||
let _ = tx.send(arc_chunk.clone());
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a task associated with this world. All tasks spawned with this method are awaited
|
/// 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;
|
let _ = sender.send((Arc::downgrade(&chunk), true)).await;
|
||||||
}
|
}
|
||||||
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
|
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
|
||||||
let sender_clone = sender.clone();
|
let (tx, rx) = oneshot::channel();
|
||||||
let level_clone = level.clone();
|
match level.pending_entity_generations.entry(pos) {
|
||||||
|
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
|
||||||
tokio::spawn(async move {
|
entry.get_mut().push(tx);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if let Ok(chunk) = rx.await {
|
||||||
let _ =
|
let _ =
|
||||||
sender_clone.send((Arc::downgrade(&chunk), true)).await;
|
sender_clone.send((Arc::downgrade(&chunk), true)).await;
|
||||||
@@ -807,7 +767,7 @@ impl Level {
|
|||||||
Arc::new(ChunkEntityData {
|
Arc::new(ChunkEntityData {
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
z: pos.y,
|
z: pos.y,
|
||||||
data: tokio::sync::Mutex::new(Vec::new()),
|
data: std::sync::Mutex::new(Vec::new()),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1025,7 +985,7 @@ mod tests {
|
|||||||
let config = LevelConfig::default();
|
let config = LevelConfig::default();
|
||||||
|
|
||||||
let overworld_level =
|
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!(
|
assert_eq!(
|
||||||
overworld_level.level_folder.dim_folder,
|
overworld_level.level_folder.dim_folder,
|
||||||
root.join("dimensions").join("minecraft").join("overworld")
|
root.join("dimensions").join("minecraft").join("overworld")
|
||||||
@@ -1038,14 +998,13 @@ mod tests {
|
|||||||
.join("region")
|
.join("region")
|
||||||
);
|
);
|
||||||
|
|
||||||
let nether_level =
|
let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
|
||||||
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
nether_level.level_folder.dim_folder,
|
nether_level.level_folder.dim_folder,
|
||||||
root.join("dimensions").join("minecraft").join("the_nether")
|
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!(
|
assert_eq!(
|
||||||
end_level.level_folder.dim_folder,
|
end_level.level_folder.dim_folder,
|
||||||
root.join("dimensions").join("minecraft").join("the_end")
|
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();
|
std::fs::create_dir_all(root.join("DIM1").join("region")).unwrap();
|
||||||
|
|
||||||
let overworld_level =
|
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);
|
assert_eq!(overworld_level.level_folder.dim_folder, root);
|
||||||
|
|
||||||
let nether_level =
|
let nether_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER);
|
||||||
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
|
|
||||||
assert_eq!(nether_level.level_folder.dim_folder, root.join("DIM-1"));
|
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"));
|
assert_eq!(end_level.level_folder.dim_folder, root.join("DIM1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
|
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
|
||||||
@@ -126,19 +125,10 @@ pub trait BlockEntity: Any + Send + Sync {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
fn set_block_state(&mut self, _block_state: BlockStateId) {}
|
fn set_block_state(&mut self, _block_state: BlockStateId) {}
|
||||||
fn on_block_replaced<'a>(
|
fn on_block_replaced(self: Arc<Self>, world: &Arc<World>, position: &BlockPos) {
|
||||||
self: Arc<Self>,
|
if let Some(inventory) = self.get_inventory() {
|
||||||
world: Arc<World>,
|
world.scatter_inventory(position, &inventory);
|
||||||
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 is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ use pumpkin_data::sound::{Sound, SoundCategory};
|
|||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::{array::from_fn, sync::Arc};
|
use std::{array::from_fn, sync::Arc};
|
||||||
@@ -63,17 +61,8 @@ impl BlockEntity for ShulkerBoxBlockEntity {
|
|||||||
.update_viewer_count::<Self>(self, world, &self.position);
|
.update_viewer_count::<Self>(self, world, &self.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_block_replaced<'a>(
|
fn on_block_replaced(self: Arc<Self>, _world: &Arc<World>, _position: &BlockPos) {
|
||||||
self: Arc<Self>,
|
// Shulker boxes retain items when broken
|
||||||
_world: Arc<World>,
|
|
||||||
_position: BlockPos,
|
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
Box::pin(async move {
|
|
||||||
// Do nothing
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
|
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
|
||||||
|
|||||||
@@ -50,84 +50,16 @@ static ERROR_CREATE_IO_FAILURE: CommandErrorType<1> = CommandErrorType::new(
|
|||||||
translation::java::COMMANDS_DATAPACK_CREATE_IO_FAILURE,
|
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> {
|
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> {
|
fn get_available_packs(server: &Server) -> Vec<String> {
|
||||||
let enabled = get_enabled_packs(server);
|
crate::data::datapack::DatapackManager::get_available_packs(server)
|
||||||
let all = get_all_known_packs(server);
|
|
||||||
all.into_iter().filter(|p| !enabled.contains(p)).collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_pack_name(server: &Server, input: &str) -> Option<String> {
|
fn find_pack_name(server: &Server, input: &str) -> Option<String> {
|
||||||
let known = get_all_known_packs(server);
|
crate::data::datapack::DatapackManager::find_pack_name(server, input)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_pack(name: &str) -> TextComponent {
|
fn format_pack(name: &str) -> TextComponent {
|
||||||
|
|||||||
@@ -267,28 +267,32 @@ struct ResolveNameExecutor;
|
|||||||
impl CommandExecutor for ResolveNameExecutor {
|
impl CommandExecutor for ResolveNameExecutor {
|
||||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||||
let name = StringArgumentType::get(context, ARG_NAME)?;
|
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_owned = name.to_string();
|
||||||
|
|
||||||
let name_component = TextComponent::text(name_owned.clone());
|
let name_component = TextComponent::text(name_owned.clone());
|
||||||
let result = futures::executor::block_on(fetch_profile_by_name_helper(server, &name_owned));
|
let runtime = server.runtime.clone();
|
||||||
match result {
|
runtime.spawn(async move {
|
||||||
Some(profile) => {
|
let result = fetch_profile_by_name_helper(&server, &name_owned).await;
|
||||||
report_resolved_profile(
|
match result {
|
||||||
&context.source,
|
Some(profile) => {
|
||||||
&profile,
|
report_resolved_profile(
|
||||||
translation::java::COMMANDS_FETCHPROFILE_NAME_SUCCESS,
|
&source,
|
||||||
name_component,
|
&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)
|
Ok(1)
|
||||||
}
|
}
|
||||||
@@ -299,27 +303,31 @@ struct ResolveIdExecutor;
|
|||||||
impl CommandExecutor for ResolveIdExecutor {
|
impl CommandExecutor for ResolveIdExecutor {
|
||||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||||
let id = UuidArgumentType::get(context, ARG_ID)?;
|
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 id_component = TextComponent::text(id.to_string());
|
||||||
let result = futures::executor::block_on(fetch_profile_by_id_helper(server, id));
|
let runtime = server.runtime.clone();
|
||||||
match result {
|
runtime.spawn(async move {
|
||||||
Some(profile) => {
|
let result = fetch_profile_by_id_helper(&server, id).await;
|
||||||
report_resolved_profile(
|
match result {
|
||||||
&context.source,
|
Some(profile) => {
|
||||||
&profile,
|
report_resolved_profile(
|
||||||
translation::java::COMMANDS_FETCHPROFILE_ID_SUCCESS,
|
&source,
|
||||||
id_component,
|
&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)
|
Ok(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ struct FillBiomeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExecutor for FillBiomeExecutor {
|
impl CommandExecutor for FillBiomeExecutor {
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||||
let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?;
|
let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?;
|
||||||
let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?;
|
let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?;
|
||||||
@@ -96,51 +95,30 @@ impl CommandExecutor for FillBiomeExecutor {
|
|||||||
|
|
||||||
let mut changed_count = 0;
|
let mut changed_count = 0;
|
||||||
for (chunk_pos, mods) in chunk_modifications {
|
for (chunk_pos, mods) in chunk_modifications {
|
||||||
let (has_replaced, count) =
|
let result = world.level.read_chunk_sync(&chunk_pos, |chunk| {
|
||||||
futures::executor::block_on(world.level.get_or_fetch_chunk(chunk_pos, |chunk| {
|
let mut local_count = 0;
|
||||||
let mut local_count = 0;
|
let mut modified = false;
|
||||||
let mut modified = false;
|
for &(rel_x, rel_y, rel_z) in &mods {
|
||||||
for &(rel_x, rel_y, rel_z) in &mods {
|
let section_index = rel_y / 4;
|
||||||
let section_index = rel_y / 4;
|
let scale_y = rel_y % 4;
|
||||||
let scale_y = rel_y % 4;
|
if let Some(current_id) =
|
||||||
if let Some(current_id) =
|
chunk
|
||||||
chunk
|
.section
|
||||||
.section
|
.get_noise_biome(section_index, rel_x, scale_y, rel_z)
|
||||||
.get_noise_biome(section_index, rel_x, scale_y, rel_z)
|
&& replace_biome_id.is_none_or(|rep| current_id == rep)
|
||||||
{
|
{
|
||||||
if let Some(replace_id) = replace_biome_id {
|
chunk
|
||||||
if current_id == replace_id {
|
.section
|
||||||
chunk.section.set_relative_biome(
|
.set_relative_biome(rel_x, rel_y, rel_z, target_biome_id);
|
||||||
rel_x,
|
local_count += 1;
|
||||||
rel_y,
|
modified = true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(modified, local_count)
|
}
|
||||||
}));
|
(local_count, modified.then(|| chunk.clone()))
|
||||||
|
});
|
||||||
|
|
||||||
if has_replaced {
|
if let Some((count, Some(chunk))) = result {
|
||||||
changed_count += count;
|
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));
|
world.broadcast_to_chunk_except(chunk_pos, &[], &CChunkData(&chunk));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,8 @@ impl CommandExecutor for ListExecutor {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
let plugins = futures::executor::block_on(server_arc.plugin_manager.active_plugins());
|
let plugins = server_arc.plugin_manager.active_plugins();
|
||||||
let loaded_plugins =
|
let loaded_plugins = server_arc.plugin_manager.loaded_plugins();
|
||||||
futures::executor::block_on(server_arc.plugin_manager.loaded_plugins());
|
|
||||||
|
|
||||||
let mut message = TextComponent::text(format!("Plugins ({}):", loaded_plugins.len()))
|
let mut message = TextComponent::text(format!("Plugins ({}):", loaded_plugins.len()))
|
||||||
.color_named(NamedColor::Gold)
|
.color_named(NamedColor::Gold)
|
||||||
@@ -59,11 +58,18 @@ impl CommandExecutor for ListExecutor {
|
|||||||
metadata.authors.join(", "),
|
metadata.authors.join(", "),
|
||||||
metadata.description
|
metadata.description
|
||||||
);
|
);
|
||||||
let component = TextComponent::text(line)
|
let mut plugin_component = TextComponent::text(line)
|
||||||
.color_named(NamedColor::Green)
|
.color_named(NamedColor::Green)
|
||||||
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
|
.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);
|
sender.send_message(message);
|
||||||
@@ -95,32 +101,38 @@ impl CommandExecutor for LoadExecutor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let plugin_name = plugin_name.to_string();
|
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!(
|
sender.send_message(TextComponent::text(format!(
|
||||||
"Plugin {plugin_name} is already loaded"
|
"Plugin {plugin_name} is already loaded"
|
||||||
)));
|
)));
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = futures::executor::block_on(
|
let sender_clone = sender.clone();
|
||||||
server_arc
|
let plugin_name_clone = plugin_name;
|
||||||
|
let server_clone = server_arc.clone();
|
||||||
|
server_arc.runtime.spawn(async move {
|
||||||
|
let result = server_clone
|
||||||
.plugin_manager
|
.plugin_manager
|
||||||
.try_load_plugin(&server_arc, Path::new(&plugin_name)),
|
.try_load_plugin(&server_clone, Path::new(&plugin_name_clone))
|
||||||
);
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
sender.send_message(
|
sender_clone.send_message(
|
||||||
TextComponent::text(format!("Plugin {plugin_name} loaded successfully"))
|
TextComponent::text(format!(
|
||||||
|
"Plugin {plugin_name_clone} loaded successfully"
|
||||||
|
))
|
||||||
.color_named(NamedColor::Green),
|
.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)
|
Ok(1)
|
||||||
}
|
}
|
||||||
@@ -149,29 +161,38 @@ impl CommandExecutor for UnloadExecutor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let plugin_name = plugin_name.to_string();
|
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!(
|
sender.send_message(TextComponent::text(format!(
|
||||||
"Plugin {plugin_name} is not loaded"
|
"Plugin {plugin_name} is not loaded"
|
||||||
)));
|
)));
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result =
|
let sender_clone = sender.clone();
|
||||||
futures::executor::block_on(server_arc.plugin_manager.unload_plugin(&plugin_name));
|
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 {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
sender.send_message(
|
sender_clone.send_message(
|
||||||
TextComponent::text(format!("Plugin {plugin_name} unloaded successfully"))
|
TextComponent::text(format!(
|
||||||
|
"Plugin {plugin_name_clone} unloaded successfully"
|
||||||
|
))
|
||||||
.color_named(NamedColor::Green),
|
.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)
|
Ok(1)
|
||||||
}
|
}
|
||||||
@@ -197,32 +218,36 @@ impl CommandExecutor for HotReloadExecutor {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sender_clone = sender.clone();
|
||||||
|
let server_clone = server_arc.clone();
|
||||||
if enabled {
|
if enabled {
|
||||||
if let Err(e) =
|
server_arc.runtime.spawn(async move {
|
||||||
futures::executor::block_on(server_arc.plugin_manager.start_watcher(&server_arc))
|
if let Err(e) = server_clone.plugin_manager.start_watcher(&server_clone).await {
|
||||||
{
|
sender_clone.send_message(TextComponent::text(format!(
|
||||||
sender.send_message(TextComponent::text(format!(
|
"Failed to start plugin watcher: {e}"
|
||||||
"Failed to start plugin watcher: {e}"
|
)));
|
||||||
)));
|
return;
|
||||||
return Ok(1);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sender.send_message(
|
sender_clone.send_message(
|
||||||
TextComponent::text("Hot reloading has been enabled.")
|
TextComponent::text("Hot reloading has been enabled.")
|
||||||
.color_named(NamedColor::Green),
|
.color_named(NamedColor::Green),
|
||||||
);
|
);
|
||||||
sender.send_message(
|
sender_clone.send_message(
|
||||||
TextComponent::text(
|
TextComponent::text(
|
||||||
"WARNING: Hot reloading can impact performance and should only be enabled during plugin development.",
|
"WARNING: Hot reloading can impact performance and should only be enabled during plugin development.",
|
||||||
)
|
)
|
||||||
.color_named(NamedColor::Red),
|
.color_named(NamedColor::Red),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
futures::executor::block_on(server_arc.plugin_manager.stop_watcher());
|
server_arc.runtime.spawn(async move {
|
||||||
sender.send_message(
|
server_clone.plugin_manager.stop_watcher().await;
|
||||||
TextComponent::text("Hot reloading has been disabled.")
|
sender_clone.send_message(
|
||||||
.color_named(NamedColor::Yellow),
|
TextComponent::text("Hot reloading has been disabled.")
|
||||||
);
|
.color_named(NamedColor::Yellow),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
|
|||||||
@@ -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() {
|
let message_text = if plugins.is_empty() {
|
||||||
TextComponent::text("No plugins are loaded on the server.").color_named(NamedColor::Red)
|
TextComponent::text("No plugins are loaded on the server.").color_named(NamedColor::Red)
|
||||||
|
|||||||
@@ -108,11 +108,9 @@ impl CommandExecutor for RideMountExecutor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Dismount first
|
// Dismount first
|
||||||
futures::executor::block_on(
|
curr_veh
|
||||||
curr_veh
|
.get_entity()
|
||||||
.get_entity()
|
.remove_passenger_sync(target.get_entity().entity_id);
|
||||||
.remove_passenger(target.get_entity().entity_id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicle
|
vehicle
|
||||||
@@ -160,7 +158,7 @@ impl CommandExecutor for RideDismountExecutor {
|
|||||||
.clone();
|
.clone();
|
||||||
if let Some(vehicle) = current_vehicle {
|
if let Some(vehicle) = current_vehicle {
|
||||||
let target_id = target.get_entity().entity_id;
|
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;
|
success_count += 1;
|
||||||
|
|
||||||
let msg = TextComponent::translate_cross(
|
let msg = TextComponent::translate_cross(
|
||||||
|
|||||||
@@ -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 true, the values are added to current rotation.
|
||||||
/// If relative flags are false, the values are absolute.
|
/// If relative flags are false, the values are absolute.
|
||||||
fn rotate_entity(
|
fn rotate_entity(
|
||||||
target: std::sync::Arc<dyn crate::entity::EntityBase>,
|
target: &std::sync::Arc<dyn crate::entity::EntityBase>,
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
is_yaw_relative: bool,
|
is_yaw_relative: bool,
|
||||||
pitch: f32,
|
pitch: f32,
|
||||||
@@ -80,7 +80,14 @@ fn rotate_entity(
|
|||||||
// This properly handles both players (sends CPlayerPosition) and other entities
|
// This properly handles both players (sends CPlayerPosition) and other entities
|
||||||
let pos = entity.pos.load();
|
let pos = entity.pos.load();
|
||||||
let world = entity.world.load_full();
|
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.
|
/// Sends success message for the rotate command.
|
||||||
@@ -107,7 +114,7 @@ impl CommandExecutor for RotateToRotationExecutor {
|
|||||||
let (yaw, yaw_rel, pitch, pitch_rel) =
|
let (yaw, yaw_rel, pitch, pitch_rel) =
|
||||||
RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?;
|
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());
|
send_success_message(sender, target.as_ref());
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
@@ -135,7 +142,7 @@ impl CommandExecutor for RotateFacingLocationExecutor {
|
|||||||
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
|
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
|
||||||
|
|
||||||
// Facing uses absolute rotation
|
// 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());
|
send_success_message(sender, target.as_ref());
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
@@ -165,7 +172,7 @@ impl CommandExecutor for RotateFacingEntityExecutor {
|
|||||||
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
|
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
|
||||||
|
|
||||||
// Facing uses absolute rotation
|
// 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());
|
send_success_message(sender, target.as_ref());
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
@@ -196,7 +203,7 @@ impl CommandExecutor for RotateFacingEntityNoAnchorExecutor {
|
|||||||
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
|
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &looking_towards);
|
||||||
|
|
||||||
// Facing uses absolute rotation
|
// 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());
|
send_success_message(sender, target.as_ref());
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
|
|||||||
@@ -26,24 +26,28 @@ impl CommandExecutor for SaveAllExecutor {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let server = context.server();
|
let server_arc = context.server().clone();
|
||||||
if let Err(err) = futures::executor::block_on(server.save_all()) {
|
let source = context.source.clone();
|
||||||
error!("Failed to save server data: {err}");
|
let runtime = server_arc.runtime.clone();
|
||||||
context.source.send_error(TextComponent::translate_cross(
|
runtime.spawn(async move {
|
||||||
translation::java::COMMANDS_SAVE_FAILED,
|
if let Err(err) = server_arc.save_all().await {
|
||||||
translation::bedrock::COMMANDS_SAVE_FAILED,
|
error!("Failed to save server data: {err}");
|
||||||
[],
|
source.send_error(TextComponent::translate_cross(
|
||||||
));
|
translation::java::COMMANDS_SAVE_FAILED,
|
||||||
} else {
|
translation::bedrock::COMMANDS_SAVE_FAILED,
|
||||||
context.source.send_feedback(
|
|
||||||
TextComponent::translate_cross(
|
|
||||||
translation::java::COMMANDS_SAVE_SUCCESS,
|
|
||||||
translation::bedrock::COMMANDS_SAVE_SUCCESS,
|
|
||||||
[],
|
[],
|
||||||
),
|
));
|
||||||
true,
|
} else {
|
||||||
);
|
source.send_feedback(
|
||||||
}
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_SAVE_SUCCESS,
|
||||||
|
translation::bedrock::COMMANDS_SAVE_SUCCESS,
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(1)
|
Ok(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
|
|||||||
let yaw = target_entity.yaw.load();
|
let yaw = target_entity.yaw.load();
|
||||||
let pitch = target_entity.pitch.load();
|
let pitch = target_entity.pitch.load();
|
||||||
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
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();
|
let target_name = target.get_display_name();
|
||||||
sender.send_message(TextComponent::translate_cross(
|
sender.send_message(TextComponent::translate_cross(
|
||||||
@@ -174,12 +178,11 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
|
|||||||
let pitch = target_entity.pitch.load();
|
let pitch = target_entity.pitch.load();
|
||||||
let player_world = player.world();
|
let player_world = player.world();
|
||||||
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
||||||
futures::executor::block_on(player.clone().teleport(
|
player.spawn_task(
|
||||||
pos,
|
player
|
||||||
Some(yaw),
|
.clone()
|
||||||
Some(pitch),
|
.teleport(pos, Some(yaw), Some(pitch), player_world),
|
||||||
player_world,
|
);
|
||||||
));
|
|
||||||
succeeded += 1;
|
succeeded += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ impl CommandExecutor for SpreadPlayersExecutor {
|
|||||||
for (index, target) in targets.iter().enumerate() {
|
for (index, target) in targets.iter().enumerate() {
|
||||||
let pile = piles[index % pile_count];
|
let pile = piles[index % pile_count];
|
||||||
let y = surface_ys[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),
|
Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
_server: &crate::server::Server,
|
server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
|
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
|
||||||
@@ -101,7 +101,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
for target in targets {
|
for target in targets {
|
||||||
futures::executor::block_on(target.clone().teleport(
|
server.runtime.spawn(target.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
@@ -148,7 +148,7 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
futures::executor::block_on(target.clone().teleport(
|
server.runtime.spawn(target.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
@@ -200,7 +200,7 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
futures::executor::block_on(target.clone().teleport(
|
server.runtime.spawn(target.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
@@ -253,7 +253,7 @@ impl CommandExecutor for EntitiesToPosWithRotationExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
futures::executor::block_on(target.clone().teleport(
|
server.runtime.spawn(target.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
@@ -304,7 +304,7 @@ impl CommandExecutor for EntitiesToPosExecutor {
|
|||||||
for target in targets {
|
for target in targets {
|
||||||
let yaw = target.get_entity().yaw.load();
|
let yaw = target.get_entity().yaw.load();
|
||||||
let pitch = target.get_entity().pitch.load();
|
let pitch = target.get_entity().pitch.load();
|
||||||
futures::executor::block_on(target.clone().teleport(
|
server.runtime.spawn(target.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
@@ -338,7 +338,7 @@ impl CommandExecutor for SelfToEntityExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
_server: &crate::server::Server,
|
server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
|
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
|
||||||
@@ -357,12 +357,9 @@ impl CommandExecutor for SelfToEntityExecutor {
|
|||||||
[],
|
[],
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
futures::executor::block_on(player.clone().teleport(
|
server
|
||||||
pos,
|
.runtime
|
||||||
Some(yaw),
|
.spawn(player.clone().teleport(pos, Some(yaw), Some(pitch), world));
|
||||||
Some(pitch),
|
|
||||||
world,
|
|
||||||
));
|
|
||||||
|
|
||||||
sender.send_message(TextComponent::translate_cross(
|
sender.send_message(TextComponent::translate_cross(
|
||||||
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
|
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
|
||||||
@@ -390,7 +387,7 @@ impl CommandExecutor for SelfToPosExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
_server: &crate::server::Server,
|
server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
match sender {
|
match sender {
|
||||||
@@ -406,7 +403,7 @@ impl CommandExecutor for SelfToPosExecutor {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let player_world = player.world();
|
let player_world = player.world();
|
||||||
futures::executor::block_on(player.clone().teleport(
|
server.runtime.spawn(player.clone().teleport(
|
||||||
pos,
|
pos,
|
||||||
Some(yaw),
|
Some(yaw),
|
||||||
Some(pitch),
|
Some(pitch),
|
||||||
|
|||||||
@@ -24,6 +24,25 @@ pub struct LoadedDatapack {
|
|||||||
pub function_count: usize,
|
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 {
|
pub struct DatapackManager {
|
||||||
loaded_packs: RwLock<Vec<LoadedDatapack>>,
|
loaded_packs: RwLock<Vec<LoadedDatapack>>,
|
||||||
functions: RwLock<HashMap<String, Vec<String>>>,
|
functions: RwLock<HashMap<String, Vec<String>>>,
|
||||||
@@ -244,9 +263,314 @@ impl DatapackManager {
|
|||||||
|
|
||||||
Ok(total_executed)
|
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");
|
let mcmeta_path = pack_path.join("pack.mcmeta");
|
||||||
if let Ok(content) = fs::read_to_string(mcmeta_path)
|
if let Ok(content) = fs::read_to_string(mcmeta_path)
|
||||||
&& let Ok(val) = serde_json::from_str::<serde_json::Value>(&content)
|
&& let Ok(val) = serde_json::from_str::<serde_json::Value>(&content)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl ServerPlayerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let storage = self.storage.clone();
|
let storage = self.storage.clone();
|
||||||
server.runtime.spawn(async move {
|
tokio::task::spawn_blocking(move || {
|
||||||
for (uuid, nbt) in snapshots {
|
for (uuid, nbt) in snapshots {
|
||||||
if let Err(e) = storage.save_player_data(&uuid, nbt) {
|
if let Err(e) = storage.save_player_data(&uuid, nbt) {
|
||||||
error!("Failed to save player data for {uuid}: {e}");
|
error!("Failed to save player data for {uuid}: {e}");
|
||||||
|
|||||||
@@ -247,3 +247,53 @@ impl Mob for CreeperEntity {
|
|||||||
true
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -535,8 +535,9 @@ impl Goal for EvokerWololoSpellGoal {
|
|||||||
|
|
||||||
for cand in candidates {
|
for cand in candidates {
|
||||||
if *cand.get_entity().entity_type == EntityType::SHEEP
|
if *cand.get_entity().entity_type == EntityType::SHEEP
|
||||||
&& let Some(mob) = cand.get_mob()
|
&& let Some(sheep) = cand
|
||||||
&& let Some(sheep) = mob.get_sheep()
|
.cast_any()
|
||||||
|
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
|
||||||
{
|
{
|
||||||
// Blue color is 11 in Minecraft
|
// Blue color is 11 in Minecraft
|
||||||
if sheep.get_color() == 11 {
|
if sheep.get_color() == 11 {
|
||||||
@@ -609,8 +610,9 @@ impl Goal for EvokerWololoSpellGoal {
|
|||||||
|
|
||||||
for cand in candidates {
|
for cand in candidates {
|
||||||
if cand.get_entity().entity_id == target_id
|
if cand.get_entity().entity_id == target_id
|
||||||
&& let Some(mob) = cand.get_mob()
|
&& let Some(sheep) = cand
|
||||||
&& let Some(sheep) = mob.get_sheep()
|
.cast_any()
|
||||||
|
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
|
||||||
{
|
{
|
||||||
// Convert color to Red (14)
|
// Convert color to Red (14)
|
||||||
sheep.set_color(14);
|
sheep.set_color(14);
|
||||||
|
|||||||
@@ -957,10 +957,6 @@ pub trait Mob: EntityBase + Send + Sync {
|
|||||||
|
|
||||||
fn mob_set_variant_name(&self, _name: &str) {}
|
fn mob_set_variant_name(&self, _name: &str) {}
|
||||||
|
|
||||||
fn get_sheep(&self) -> Option<&crate::entity::passive::sheep::SheepEntity> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mob_on_lightning_strike(
|
fn mob_on_lightning_strike(
|
||||||
&self,
|
&self,
|
||||||
caller: &dyn EntityBase,
|
caller: &dyn EntityBase,
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ impl ShulkerEntity {
|
|||||||
.unwrap_or(DEFAULT_ATTACH_FACE)
|
.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);
|
self.attach_face.store(face as u8, Ordering::Relaxed);
|
||||||
let entity = &self.mob_entity.living_entity.entity;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
entity.send_meta_data(
|
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 {
|
pub fn get_raw_peek(&self) -> u8 {
|
||||||
self.peek_amount.load(Ordering::Relaxed)
|
self.peek_amount.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,3 +52,46 @@ impl Mob for ZombieEntity {
|
|||||||
self.entity.mob_read_nbt(nbt);
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2351,29 +2351,18 @@ impl Entity {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let world_for_dest = world_clone.clone();
|
let world_for_dest = world_clone.clone();
|
||||||
let caller_for_dest = caller_clone.clone();
|
let caller_for_dest = caller_clone.clone();
|
||||||
let gen_pool = world_for_dest.level.gen_pool.clone();
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
let transition = if let Some(pool) = gen_pool {
|
rayon::spawn(move || {
|
||||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
let dest = portal_type.get_portal_destination(
|
||||||
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(
|
|
||||||
&world_for_dest,
|
&world_for_dest,
|
||||||
dest_world_opt,
|
dest_world_opt,
|
||||||
&caller_for_dest,
|
&caller_for_dest,
|
||||||
entry_pos,
|
entry_pos,
|
||||||
src_portal.as_ref(),
|
src_portal.as_ref(),
|
||||||
)
|
);
|
||||||
};
|
let _ = tx.send(dest);
|
||||||
|
});
|
||||||
|
let transition = rx.await.ok().flatten();
|
||||||
|
|
||||||
if let Some(transition) = transition {
|
if let Some(transition) = transition {
|
||||||
let dest_world = transition.new_world.clone();
|
let dest_world = transition.new_world.clone();
|
||||||
@@ -3164,7 +3153,7 @@ impl Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn teleport(
|
pub fn teleport(
|
||||||
&self,
|
&self,
|
||||||
position: Vector3<f64>,
|
position: Vector3<f64>,
|
||||||
yaw: Option<f32>,
|
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) {
|
pub async fn remove_passenger(&self, passenger_id: i32) {
|
||||||
self.remove_passenger_internal(passenger_id, true).await;
|
self.remove_passenger_internal(passenger_id, true).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,9 +119,6 @@ impl IronGolemEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mob for IronGolemEntity {
|
impl Mob for IronGolemEntity {
|
||||||
fn as_iron_golem(&self) -> Option<&IronGolemEntity> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
|
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
|
||||||
nbt.put_bool("PlayerCreated", self.is_player_created());
|
nbt.put_bool("PlayerCreated", self.is_player_created());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,10 +156,6 @@ impl Mob for SheepEntity {
|
|||||||
self.set_sheared(false);
|
self.set_sheared(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sheep(&self) -> Option<&SheepEntity> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
|
fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
|
||||||
use super::animal::Animal;
|
use super::animal::Animal;
|
||||||
self.animal_interact(player, item_stack, Sound::EntitySheepAmbient)
|
self.animal_interact(player, item_stack, Sound::EntitySheepAmbient)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -411,8 +411,8 @@ pub struct ChunkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkManager {
|
impl ChunkManager {
|
||||||
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 10;
|
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 16;
|
||||||
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(250);
|
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ impl ItemBehaviour for ShearsItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn use_on_entity(&self, _item: &mut ItemStack, player: &Player, entity: Arc<dyn EntityBase>) {
|
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.is_sheared()
|
||||||
{
|
{
|
||||||
sheep.set_sheared(true);
|
sheep.set_sheared(true);
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ static MAIN_THREAD: OnceLock<ThreadId> = OnceLock::new();
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
let _ = MAIN_THREAD.set(thread::current().id());
|
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.
|
// Set the panic handler.
|
||||||
std::panic::set_hook(Box::new(handle_panic));
|
std::panic::set_hook(Box::new(handle_panic));
|
||||||
|
|
||||||
|
|||||||
@@ -13,45 +13,60 @@ impl JavaClient {
|
|||||||
if version.supports_configuration_state() {
|
if version.supports_configuration_state() {
|
||||||
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
|
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
|
||||||
.await;
|
.await;
|
||||||
let registry = Registry::get_synced(version);
|
let registry_packets = tokio::task::spawn_blocking(move || {
|
||||||
let mut sent_dimension_type = false;
|
let registry = Registry::get_synced(version);
|
||||||
for reg in ®istry {
|
let mut packets = Vec::new();
|
||||||
if reg.registry_id == "minecraft:dimension_type" {
|
let mut sent_dimension_type = false;
|
||||||
sent_dimension_type = true;
|
for reg in ®istry {
|
||||||
|
if reg.registry_id == "minecraft:dimension_type" {
|
||||||
|
sent_dimension_type = true;
|
||||||
|
}
|
||||||
|
let packet = CRegistryData::new(®.registry_id, ®.registry_entries);
|
||||||
|
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
|
||||||
|
packets.push(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.send_packet(&CRegistryData::new(®.registry_id, ®.registry_entries))
|
if !sent_dimension_type {
|
||||||
.await;
|
let dims = [
|
||||||
}
|
&pumpkin_data::dimension::Dimension::OVERWORLD,
|
||||||
if !sent_dimension_type {
|
&pumpkin_data::dimension::Dimension::OVERWORLD_CAVES,
|
||||||
let dims = [
|
&pumpkin_data::dimension::Dimension::THE_END,
|
||||||
&pumpkin_data::dimension::Dimension::OVERWORLD,
|
&pumpkin_data::dimension::Dimension::THE_NETHER,
|
||||||
&pumpkin_data::dimension::Dimension::OVERWORLD_CAVES,
|
];
|
||||||
&pumpkin_data::dimension::Dimension::THE_END,
|
let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims
|
||||||
&pumpkin_data::dimension::Dimension::THE_NETHER,
|
.iter()
|
||||||
];
|
.map(|dim| pumpkin_data::registry::RegistryEntryData {
|
||||||
let dim_entries: Vec<pumpkin_data::registry::RegistryEntryData> = dims
|
entry_id: dim.minecraft_name.to_string(),
|
||||||
.iter()
|
data: Some(build_dimension_nbt(dim).into_boxed_slice()),
|
||||||
.map(|dim| pumpkin_data::registry::RegistryEntryData {
|
})
|
||||||
entry_id: dim.minecraft_name.to_string(),
|
.collect();
|
||||||
data: Some(build_dimension_nbt(dim).into_boxed_slice()),
|
let dim_type = "minecraft:dimension_type".to_string();
|
||||||
})
|
let packet = CRegistryData::new(&dim_type, &dim_entries);
|
||||||
.collect();
|
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
|
||||||
self.send_packet(&CRegistryData::new(
|
packets.push(data);
|
||||||
&"minecraft:dimension_type".to_string(),
|
}
|
||||||
&dim_entries,
|
}
|
||||||
))
|
let mut tags = Vec::new();
|
||||||
.await;
|
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
|
// We are done with configuring
|
||||||
self.send_packet(&CFinishConfig).await;
|
self.send_packet(&CFinishConfig).await;
|
||||||
|
|||||||
@@ -344,51 +344,84 @@ impl JavaClient {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.version.load() >= JavaMinecraftVersion::V_1_20_2 {
|
let mut valid_chunks = Vec::with_capacity(chunks.len());
|
||||||
self.send_packet(&CChunkBatchStart).await;
|
|
||||||
}
|
|
||||||
for chunk in chunks {
|
for chunk in chunks {
|
||||||
let mut event = ChunkSend::new(player.world(), chunk.clone());
|
let mut event = ChunkSend::new(player.world(), chunk.clone());
|
||||||
server.plugin_manager.fire(&server, &mut event).await;
|
server.plugin_manager.fire(&server, &mut event).await;
|
||||||
if event.cancelled {
|
if !event.cancelled {
|
||||||
continue;
|
valid_chunks.push(chunk.clone());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
if valid_chunks.is_empty() {
|
||||||
let version = self.version.load();
|
return;
|
||||||
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 version >= JavaMinecraftVersion::V_1_14 && version < JavaMinecraftVersion::V_1_18 {
|
let version = self.version.load();
|
||||||
match CLightUpdate::from_chunk(chunk, version) {
|
// Offload CPU-heavy packet serialization (palette packing, lighting, NBT heightmaps)
|
||||||
Ok(light_packet) => {
|
// off Tokio worker threads onto blocking/Rayon worker threads
|
||||||
let mut light_buf = Vec::new();
|
let serialize_tasks: Vec<_> = valid_chunks
|
||||||
if let Err(err) =
|
.into_iter()
|
||||||
light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version)))
|
.map(|chunk| {
|
||||||
{
|
tokio::task::spawn_blocking(move || {
|
||||||
error!("Failed to write light update id: {err:?}");
|
let mut buf = Vec::with_capacity(32 * 1024);
|
||||||
} else if let Err(err) =
|
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
|
||||||
light_packet.write_packet_data(&mut light_buf, &version)
|
error!("Failed to write chunk data id: {err:?}");
|
||||||
{
|
return None;
|
||||||
error!("Failed to write light update data: {err:?}");
|
}
|
||||||
} else {
|
if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) {
|
||||||
self.send_packet_now_data(light_buf.into()).await;
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
Err(err) => {
|
None
|
||||||
error!("Failed to create light update packet: {err:?}");
|
};
|
||||||
}
|
|
||||||
|
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))
|
self.send_packet(&CChunkBatchEnd::new(chunks.len() as u16))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ async fn handle_packet(
|
|||||||
let plugins = server
|
let plugins = server
|
||||||
.plugin_manager
|
.plugin_manager
|
||||||
.active_plugins()
|
.active_plugins()
|
||||||
.await
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|meta| meta.name)
|
.map(|meta| meta.name)
|
||||||
.reduce(|acc, name| format!("{acc}, {name}"))
|
.reduce(|acc, name| format!("{acc}, {name}"))
|
||||||
|
|||||||
@@ -316,9 +316,9 @@ impl Context {
|
|||||||
&self,
|
&self,
|
||||||
loader: Arc<dyn crate::plugin::loader::PluginLoader>,
|
loader: Arc<dyn crate::plugin::loader::PluginLoader>,
|
||||||
) -> bool {
|
) -> 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;
|
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
|
// Return true if any new plugins were loaded
|
||||||
after_count > before_count
|
after_count > before_count
|
||||||
|
|||||||
@@ -70,8 +70,22 @@ pub type EnchantmentManagerResource =
|
|||||||
pub type OpManagerResource = WasmResource<Arc<Server>>;
|
pub type OpManagerResource = WasmResource<Arc<Server>>;
|
||||||
pub type BanManagerResource = WasmResource<Arc<Server>>;
|
pub type BanManagerResource = WasmResource<Arc<Server>>;
|
||||||
pub type WhitelistManagerResource = 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>>;
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct ContainerBlockEntity {
|
pub struct ContainerBlockEntity {
|
||||||
pub provider: Arc<dyn crate::block::entities::BlockEntity>,
|
pub provider: Arc<dyn crate::block::entities::BlockEntity>,
|
||||||
@@ -195,6 +209,24 @@ impl PluginHostState {
|
|||||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
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>(
|
pub fn add_world<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: Arc<World>,
|
provider: Arc<World>,
|
||||||
@@ -362,6 +394,34 @@ impl PluginHostState {
|
|||||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
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>(
|
pub fn add_block_entity<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: Arc<dyn crate::block::entities::BlockEntity>,
|
provider: Arc<dyn crate::block::entities::BlockEntity>,
|
||||||
|
|||||||
@@ -319,6 +319,24 @@ impl HostContainerBlockEntity for PluginHostState {
|
|||||||
self.add_block_entity(provider)
|
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> {
|
async fn get_size(&mut self, res: Resource<ContainerBlockEntity>) -> wasmtime::Result<u32> {
|
||||||
let container = self
|
let container = self
|
||||||
.resource_table
|
.resource_table
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,27 +3,18 @@ use wasmtime::component::Resource;
|
|||||||
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
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::{
|
use crate::plugin::loader::wasm::wasm_host::{
|
||||||
state::{EntityResource, PluginHostState},
|
state::{EntityResource, PluginHostState},
|
||||||
wit::v0_1::events::to_wasm_position,
|
wit::v0_1::events::to_wasm_position,
|
||||||
wit::v0_1::pumpkin::plugin::{
|
wit::v0_1::pumpkin::plugin::{
|
||||||
attributes::{
|
|
||||||
Attribute, AttributeModifier as WitAttributeModifier,
|
|
||||||
ModifierOperation as WitModifierOperation,
|
|
||||||
},
|
|
||||||
common::{EntityPose, NbtTree as WitNbtTree, Position},
|
common::{EntityPose, NbtTree as WitNbtTree, Position},
|
||||||
damage_types::DamageType as WitDamageType,
|
|
||||||
entity::Host,
|
entity::Host,
|
||||||
entity_types,
|
entity_types,
|
||||||
item_stack::ItemStack as WitHostItemStack,
|
|
||||||
text::TextComponent,
|
text::TextComponent,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
world::{
|
world::{
|
||||||
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity,
|
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
|
||||||
EquipmentSlot as WitEquipmentSlot, HostEntity,
|
LivingEntity as WitLivingEntity, Mob as WitMob,
|
||||||
RayTraceBlockResult as WitRayTraceBlockResult,
|
RayTraceBlockResult as WitRayTraceBlockResult,
|
||||||
RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult,
|
RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult,
|
||||||
World,
|
World,
|
||||||
@@ -37,7 +28,7 @@ use pumpkin_data::entity::EntityPose as InternalEntityPose;
|
|||||||
impl Host for PluginHostState {}
|
impl Host for PluginHostState {}
|
||||||
impl entity_types::Host for PluginHostState {}
|
impl entity_types::Host for PluginHostState {}
|
||||||
|
|
||||||
fn entity_from_resource(
|
pub fn entity_from_resource(
|
||||||
state: &PluginHostState,
|
state: &PluginHostState,
|
||||||
entity: &Resource<Entity>,
|
entity: &Resource<Entity>,
|
||||||
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
|
) -> 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 {
|
impl HostEntity for PluginHostState {
|
||||||
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
|
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
|
||||||
let entity = entity_from_resource(self, &entity)?;
|
let entity = entity_from_resource(self, &entity)?;
|
||||||
@@ -526,300 +411,6 @@ impl HostEntity for PluginHostState {
|
|||||||
Ok(())
|
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> {
|
async fn get_fall_distance(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
|
||||||
let entity = entity_from_resource(self, &entity)?;
|
let entity = entity_from_resource(self, &entity)?;
|
||||||
Ok(entity
|
Ok(entity
|
||||||
@@ -1155,144 +746,12 @@ impl HostEntity for PluginHostState {
|
|||||||
Ok(crate::entity::breath::MAX_AIR)
|
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<()> {
|
async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
|
||||||
let entity = entity_from_resource(self, &entity)?;
|
let entity = entity_from_resource(self, &entity)?;
|
||||||
entity.get_entity().remove();
|
entity.get_entity().remove();
|
||||||
Ok(())
|
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(
|
async fn raycast(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Resource<Entity>,
|
entity: Resource<Entity>,
|
||||||
@@ -1429,6 +888,40 @@ impl HostEntity for PluginHostState {
|
|||||||
Ok(base_entity.has_custom_data(&namespace, &key))
|
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<()> {
|
async fn drop(&mut self, rep: Resource<Entity>) -> wasmtime::Result<()> {
|
||||||
let _ = self
|
let _ = self
|
||||||
.resource_table
|
.resource_table
|
||||||
@@ -1436,161 +929,3 @@ impl HostEntity for PluginHostState {
|
|||||||
Ok(())
|
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ use crate::plugin::{
|
|||||||
loader::wasm::wasm_host::{
|
loader::wasm::wasm_host::{
|
||||||
state::PluginHostState,
|
state::PluginHostState,
|
||||||
wit::v0_1::{
|
wit::v0_1::{
|
||||||
entity::{from_wit_damage_type, to_wit_damage_type},
|
|
||||||
events::{
|
events::{
|
||||||
ToFromWasmEvent, cleanup_event, consume_player, consume_text_component,
|
ToFromWasmEvent, cleanup_event, consume_player, consume_text_component,
|
||||||
consume_world, from_wasm_block_position, from_wasm_position,
|
consume_world, from_wasm_block_position, from_wasm_position,
|
||||||
to_wasm_block_position, to_wasm_position,
|
to_wasm_block_position, to_wasm_position,
|
||||||
},
|
},
|
||||||
|
living_entity::{from_wit_damage_type, to_wit_damage_type},
|
||||||
pumpkin::plugin::event::{
|
pumpkin::plugin::event::{
|
||||||
AreaEffectCloudApplyEventData, ArrowBodyCountChangeEventData,
|
AreaEffectCloudApplyEventData, ArrowBodyCountChangeEventData,
|
||||||
BatToggleSleepEventData, CreatureSpawnEventData, CreeperPowerEventData,
|
BatToggleSleepEventData, CreatureSpawnEventData, CreeperPowerEventData,
|
||||||
|
|||||||
@@ -118,6 +118,23 @@ impl gui::HostGui for PluginHostState {
|
|||||||
self.add_gui(gui)
|
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(
|
async fn set_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: Resource<Gui>,
|
res: Resource<Gui>,
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
941
crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs
Normal file
941
crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ pub mod common;
|
|||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod context;
|
pub mod context;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
|
pub mod datapack;
|
||||||
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod display;
|
pub mod display;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod enchantment;
|
pub mod enchantment;
|
||||||
@@ -34,12 +36,18 @@ pub mod generated_packets;
|
|||||||
pub mod gui;
|
pub mod gui;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod i18n;
|
pub mod i18n;
|
||||||
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
|
pub mod inventory;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod item_stack;
|
pub mod item_stack;
|
||||||
pub mod java_dialogs;
|
pub mod java_dialogs;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
|
pub mod living_entity;
|
||||||
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
|
pub mod mob;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
#[allow(clippy::unused_async_trait_impl)]
|
#[allow(clippy::unused_async_trait_impl)]
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ use crate::{
|
|||||||
GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource,
|
GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource,
|
||||||
},
|
},
|
||||||
wit::v0_1::{
|
wit::v0_1::{
|
||||||
entity::from_wit_damage_type,
|
|
||||||
events::{
|
events::{
|
||||||
from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position,
|
from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position,
|
||||||
},
|
},
|
||||||
|
living_entity::from_wit_damage_type,
|
||||||
pumpkin::{
|
pumpkin::{
|
||||||
self,
|
self,
|
||||||
plugin::damage_types::DamageType as WitDamageType,
|
plugin::damage_types::DamageType as WitDamageType,
|
||||||
@@ -1136,6 +1136,34 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
Ok(())
|
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(
|
async fn get_inventory_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::plugin::{
|
|||||||
wit::v0_1::pumpkin::{
|
wit::v0_1::pumpkin::{
|
||||||
self,
|
self,
|
||||||
plugin::{
|
plugin::{
|
||||||
|
datapack::DatapackManager as WitDatapackManager,
|
||||||
player::{BanIpOptions, BanPlayerOptions, Player},
|
player::{BanIpOptions, BanPlayerOptions, Player},
|
||||||
server::{
|
server::{
|
||||||
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
|
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
|
||||||
@@ -606,6 +607,17 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
Ok(ids)
|
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<()> {
|
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
|
||||||
self.resource_table
|
self.resource_table
|
||||||
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
||||||
|
|||||||
@@ -2231,7 +2231,7 @@ impl WasmChunkGenerator {
|
|||||||
proto_chunk,
|
proto_chunk,
|
||||||
};
|
};
|
||||||
|
|
||||||
futures::executor::block_on(async {
|
let run = async {
|
||||||
let mut store = self.plugin.store.lock().await;
|
let mut store = self.plugin.store.lock().await;
|
||||||
let Ok(buffer_res) = store.data_mut().add_chunk_buffer(chunk_buffer) else {
|
let Ok(buffer_res) = store.data_mut().add_chunk_buffer(chunk_buffer) else {
|
||||||
return;
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1009,8 +1009,8 @@ impl PluginManager {
|
|||||||
|
|
||||||
/// Checks if plugin active
|
/// Checks if plugin active
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub async fn is_plugin_active(&self, name: &str) -> bool {
|
pub fn is_plugin_active(&self, name: &str) -> bool {
|
||||||
let plugins = self.plugins.read().await;
|
let plugins = self.plugins.blocking_read();
|
||||||
plugins
|
plugins
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| p.metadata.name == name && p.is_active && p.instance.is_some())
|
.any(|p| p.metadata.name == name && p.is_active && p.instance.is_some())
|
||||||
@@ -1018,8 +1018,8 @@ impl PluginManager {
|
|||||||
|
|
||||||
/// Get list of active plugins
|
/// Get list of active plugins
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub async fn active_plugins(&self) -> Vec<PluginMetadata> {
|
pub fn active_plugins(&self) -> Vec<PluginMetadata> {
|
||||||
let plugins = self.plugins.read().await;
|
let plugins = self.plugins.blocking_read();
|
||||||
plugins
|
plugins
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| p.is_active && p.instance.is_some())
|
.filter(|p| p.is_active && p.instance.is_some())
|
||||||
@@ -1029,15 +1029,15 @@ impl PluginManager {
|
|||||||
|
|
||||||
/// Checks if plugin loaded
|
/// Checks if plugin loaded
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub async fn is_plugin_loaded(&self, name: &str) -> bool {
|
pub fn is_plugin_loaded(&self, name: &str) -> bool {
|
||||||
let plugins = self.plugins.read().await;
|
let plugins = self.plugins.blocking_read();
|
||||||
plugins.iter().any(|p| p.metadata.name == name)
|
plugins.iter().any(|p| p.metadata.name == name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get list of loaded plugins
|
/// Get list of loaded plugins
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub async fn loaded_plugins(&self) -> Vec<PluginMetadata> {
|
pub fn loaded_plugins(&self) -> Vec<PluginMetadata> {
|
||||||
let plugins = self.plugins.read().await;
|
let plugins = self.plugins.blocking_read();
|
||||||
plugins.iter().map(|p| p.metadata.clone()).collect()
|
plugins.iter().map(|p| p.metadata.clone()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -315,16 +315,6 @@ impl Server {
|
|||||||
};
|
};
|
||||||
let server = Arc::new(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
|
// Fetch / generate keys in background tasks to avoid blocking startup
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@@ -400,7 +390,6 @@ impl Server {
|
|||||||
let l_info = server.level_info.clone(); // Access from struct
|
let l_info = server.level_info.clone(); // Access from struct
|
||||||
let weak = Arc::downgrade(&server);
|
let weak = Arc::downgrade(&server);
|
||||||
let config = Arc::new(server.advanced_config.world.clone());
|
let config = Arc::new(server.advanced_config.world.clone());
|
||||||
let pool = gen_pool.clone();
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
info!(
|
info!(
|
||||||
@@ -409,7 +398,7 @@ impl Server {
|
|||||||
.color_named(NamedColor::DarkGreen)
|
.color_named(NamedColor::DarkGreen)
|
||||||
.to_pretty_console()
|
.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 world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak));
|
||||||
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
||||||
level.world_portal.store(Arc::new(Some(portal)));
|
level.world_portal.store(Arc::new(Some(portal)));
|
||||||
@@ -508,14 +497,8 @@ impl Server {
|
|||||||
let config = Arc::new(server.advanced_config.world.clone());
|
let config = Arc::new(server.advanced_config.world.clone());
|
||||||
let seed = server.level_info.load().world_gen_settings.seed;
|
let seed = server.level_info.load().world_gen_settings.seed;
|
||||||
|
|
||||||
// TODO: gen_pool should be reused
|
let level =
|
||||||
let level = pumpkin_world::dimension::into_level(
|
pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed);
|
||||||
dimension.clone(),
|
|
||||||
&config,
|
|
||||||
world_path,
|
|
||||||
seed,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let world: World = World::load(level.clone(), l_info, dimension, registry, weak);
|
let world: World = World::load(level.clone(), l_info, dimension, registry, weak);
|
||||||
let world = Arc::new(world);
|
let world = Arc::new(world);
|
||||||
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ use pumpkin_nbt::compound::NbtCompound;
|
|||||||
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
|
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
|
||||||
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
|
CBlockUpdate, CDisguisedChatMessage, CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent,
|
||||||
CLightUpdate, CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData,
|
PlayerSpawnData,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
|
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
|
||||||
@@ -511,7 +511,11 @@ impl World {
|
|||||||
let mut nbt = NbtCompound::new();
|
let mut nbt = NbtCompound::new();
|
||||||
entity.write_nbt(&mut nbt);
|
entity.write_nbt(&mut nbt);
|
||||||
let chunk = self.level.get_entity_chunk(current_chunk).await;
|
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);
|
chunk.mark_dirty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3144,20 +3148,23 @@ impl World {
|
|||||||
if client.version.load() < JavaMinecraftVersion::V_1_20_2
|
if client.version.load() < JavaMinecraftVersion::V_1_20_2
|
||||||
&& client.version.load() >= JavaMinecraftVersion::V_1_13
|
&& client.version.load() >= JavaMinecraftVersion::V_1_13
|
||||||
{
|
{
|
||||||
let mut tags = Vec::new();
|
|
||||||
let version = client.version.load();
|
let version = client.version.load();
|
||||||
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
|
if let Ok(Ok(packet_data)) = tokio::task::spawn_blocking(move || {
|
||||||
if pumpkin_data::tag::get_registry_key_tags(version, key)
|
let mut tags = Vec::new();
|
||||||
.is_some_and(|map| !map.is_empty())
|
for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS {
|
||||||
{
|
if pumpkin_data::tag::get_registry_key_tags(version, key)
|
||||||
tags.push(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) {
|
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
|
||||||
@@ -3205,19 +3212,7 @@ impl World {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
|
client.send_chunks(&[chunk]).await;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let velocity = player.living_entity.entity.velocity.load();
|
let velocity = player.living_entity.entity.velocity.load();
|
||||||
|
|
||||||
@@ -4245,20 +4240,7 @@ impl World {
|
|||||||
.level
|
.level
|
||||||
.get_or_fetch_chunk(center_chunk, std::clone::Clone::clone)
|
.get_or_fetch_chunk(center_chunk, std::clone::Clone::clone)
|
||||||
.await;
|
.await;
|
||||||
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
|
java_client.send_chunks(&[chunk]).await;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send teleport packet after at least the center chunk was delivered
|
// 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
|
// truth, so the chunk's NBT is taken (cleared) to avoid keeping
|
||||||
// a duplicate copy that would be re-appended on the next unload
|
// a duplicate copy that would be re-appended on the next unload
|
||||||
// and doubled on every reload.
|
// 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>> =
|
let mut entities_to_add: Vec<Arc<dyn EntityBase>> =
|
||||||
Vec::with_capacity(entity_nbts.len());
|
Vec::with_capacity(entity_nbts.len());
|
||||||
for entity_nbt in &entity_nbts {
|
for entity_nbt in &entity_nbts {
|
||||||
|
|||||||
Reference in New Issue
Block a user