diff --git a/pumpkin-inventory/src/screen_handler.rs b/pumpkin-inventory/src/screen_handler.rs index 6effe64c7..f3243543d 100644 --- a/pumpkin-inventory/src/screen_handler.rs +++ b/pumpkin-inventory/src/screen_handler.rs @@ -773,7 +773,7 @@ pub trait ScreenHandler: Send + Sync { } /// Cancels any client-side changes and resynchronizes the state. - fn cancel<'a>(&'a mut self, _player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> { + fn cancel(&mut self) -> ScreenHandlerFuture<'_, ()> { Box::pin(async move { self.sync_state().await; }) diff --git a/pumpkin-plugin-wit/v0.1/player.wit b/pumpkin-plugin-wit/v0.1/player.wit index 397f54bab..111bc9429 100644 --- a/pumpkin-plugin-wit/v0.1/player.wit +++ b/pumpkin-plugin-wit/v0.1/player.wit @@ -25,6 +25,25 @@ interface player { walk-speed: f32, } + /// Represents a player's skin textures and metadata. + record player-skin { + /// The base64-encoded texture data (usually JSON containing skin/cape URLs). + value: string, + /// The optional signature for the texture data. + signature: option, + } + + /// Flags representing visible skin parts. + flags skin-parts { + cape, + jacket, + left-sleeve, + right-sleeve, + left-pants-leg, + right-pants-leg, + hat, + } + /// A handle to a player on the server. resource player { /// Returns the player as an entity. @@ -162,36 +181,6 @@ interface player { /// Adds experience points to the player. add-experience-points: func(points: s32); - /// Returns whether the player is currently sneaking. - is-sneaking: func() -> bool; - /// Sets whether the player is currently sneaking. - set-sneaking: func(sneaking: bool); - /// Returns whether the player is currently sprinting. - is-sprinting: func() -> bool; - /// Sets whether the player is currently sprinting. - set-sprinting: func(sprinting: bool); - /// Returns whether the player is currently swimming. - is-swimming: func() -> bool; - /// Sets whether the player is currently swimming. - set-swimming: func(swimming: bool); - /// Returns whether the player is currently invisible. - is-invisible: func() -> bool; - /// Sets whether the player is currently invisible. - set-invisible: func(invisible: bool); - /// Returns whether the player is currently glowing. - is-glowing: func() -> bool; - /// Sets whether the player is currently glowing. - set-glowing: func(glowing: bool); - /// Returns whether the player is currently flying due to a fall. - is-fall-flying: func() -> bool; - /// Sets whether the player is currently flying due to a fall. - set-fall-flying: func(fall-flying: bool); - /// Returns whether the player is currently on fire. Note: this is only visually like fire, there is no actual logic. - is-on-fire: func() -> bool; - /// Sets whether the player is visually on fire. Note: this is only visually like fire, there is no actual logic. - set-on-fire: func(on-fire: bool); - /// Returns whether the player is currently on the ground. - is-on-ground: func() -> bool; /// Returns whether the player is currently flying. is-flying: func() -> bool; /// Sets whether the player should be flying. @@ -204,5 +193,13 @@ interface player { /// Returns the player's IP address. get-ip: func() -> string; + + /// Returns the player's current skin textures. + get-skin: func() -> option; + + /// Returns the currently visible skin parts. + get-skin-parts: func() -> skin-parts; + /// Sets which skin parts are visible. + set-skin-parts: func(parts: skin-parts); } } diff --git a/pumpkin-plugin-wit/v0.1/server.wit b/pumpkin-plugin-wit/v0.1/server.wit index b72de2e0c..a84343ca8 100644 --- a/pumpkin-plugin-wit/v0.1/server.wit +++ b/pumpkin-plugin-wit/v0.1/server.wit @@ -16,6 +16,18 @@ interface server { hard, } + /// Represents the three main Minecraft dimensions. + enum dimension { + overworld, + nether, + end, + } + + variant command-sender { + console, + player(player), + } + /// Represents the global server instance. resource server { /// Returns the current difficulty level of the server. @@ -47,6 +59,12 @@ interface server { /// Gets a world by its dimension name (e.g., "minecraft:overworld"). get-world-by-name: func(name: string) -> option<%world>; + execute-command: func(command: string, sender: command-sender); + + /// Creates or loads a world by its name and dimension. + /// If a world with the given name already exists, it will be returned. + create-world: func(name: string, dimension: dimension) -> %world; + /// Broadcasts a system chat message to all players. broadcast: func(message: string); diff --git a/pumpkin-plugin-wit/v0.1/world.wit b/pumpkin-plugin-wit/v0.1/world.wit index a43e0b81d..5e35fc169 100644 --- a/pumpkin-plugin-wit/v0.1/world.wit +++ b/pumpkin-plugin-wit/v0.1/world.wit @@ -114,10 +114,27 @@ interface %world { get-head-yaw: func() -> f32; is-on-ground: func() -> bool; + is-sneaking: func() -> bool; + set-sneaking: func(sneaking: bool); + is-sprinting: func() -> bool; + set-sprinting: func(sprinting: bool); + + is-swimming: func() -> bool; + set-swimming: func(swimming: bool); + is-invisible: func() -> bool; + set-invisible: func(invisible: bool); + is-glowing: func() -> bool; + set-glowing: func(glowing: bool); + + is-fall-flying: func() -> bool; + set-fall-flying: func(fall-flying: bool); + + is-on-fire: func() -> bool; + set-on-fire: func(on-fire: bool); teleport: func(pos: position, world-ref: %world); set-velocity: func(velocity: position); @@ -137,6 +154,27 @@ interface %world { set-fire-ticks: func(ticks: s32); remove: func(); + + /// Performs a raycast from the entity's eye position in its looking direction. + raycast: func(max-distance: f64, fluid-handling: bool) -> option; + } + + /// Result of a raycast operation. + record raycast-result { + /// The block position that was hit. + pos: block-pos, + /// The face of the block that was hit. + face: block-direction, + } + + /// Represents a cardinal direction or block face. + enum block-direction { + down, + up, + north, + south, + west, + east, } /// Represents a Minecraft world (dimension). diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index f09364818..c0c9a6370 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -106,6 +106,21 @@ impl Vector3 { } } +impl Vector3 { + #[must_use] + pub fn from_yaw_pitch(yaw: f32, pitch: f32) -> Self { + let yaw_rad = f64::from(yaw).to_radians(); + let pitch_rad = f64::from(pitch).to_radians(); + + let cos_pitch = pitch_rad.cos(); + let sin_pitch = pitch_rad.sin(); + let cos_yaw = yaw_rad.cos(); + let sin_yaw = yaw_rad.sin(); + + Self::new(-cos_pitch * sin_yaw, -sin_pitch, cos_pitch * cos_yaw) + } +} + impl Vector3 { /// Calculates the squared length (magnitude) of the vector. /// diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 377d26ff7..5f1fd039e 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -136,6 +136,15 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any { self } + fn get_eye_pos(&self) -> Vector3 { + self.get_entity().get_eye_pos() + } + + fn get_looking_vector(&self) -> Vector3 { + let entity = self.get_entity(); + Vector3::from_yaw_pitch(entity.yaw.load(), entity.pitch.load()) + } + fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> { Box::pin(async move { let entity = self.get_entity(); diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 72a22e574..df7407066 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -88,7 +88,6 @@ use crate::entity::{EntityBaseFuture, NbtFuture, TeleportFuture}; use crate::net::{ClientPlatform, GameProfile}; use crate::net::{DisconnectReason, PlayerConfig}; use crate::plugin::player::exp_change::PlayerExpChangeEvent; -use crate::plugin::player::inventory_close::InventoryCloseEvent; use crate::plugin::player::inventory_interact::InventoryClickEvent; use crate::plugin::player::player_change_world::PlayerChangeWorldEvent; use crate::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent; @@ -466,14 +465,9 @@ pub struct Player { pub tab_list_order: AtomicI32, pub tab_list_latency: AtomicI32, pub tab_list_listed: AtomicBool, - pub this: Weak, } impl Player { - pub fn this(&self) -> Arc { - self.this.upgrade().expect("Player should have a reference") - } - #[expect(clippy::too_many_lines)] pub async fn new( client: ClientPlatform, @@ -603,7 +597,6 @@ impl Player { tab_list_order: AtomicI32::new(0), tab_list_latency: AtomicI32::new(0), tab_list_listed: AtomicBool::new(false), - this: Weak::new(), } } @@ -2897,20 +2890,21 @@ impl Player { } pub async fn on_handled_screen_closed(&self) { - let window_type = { - let handler_lock = self.current_screen_handler.lock().await; - let mut handler = handler_lock.lock().await; - let wt = handler.window_type(); - handler.on_closed(self).await; - wt - }; + // let window_type = { + // let handler_lock = self.current_screen_handler.lock().await; + // let mut handler = handler_lock.lock().await; + // let wt = handler.window_type(); + // handler.on_closed(self).await; + // wt + // }; - if let Some(server) = self.living_entity.entity.world.load().server.upgrade() { - server - .plugin_manager - .fire(InventoryCloseEvent::new(&self.this(), window_type)) - .await; - } + // TODO + // if let Some(server) = self.living_entity.entity.world.load().server.upgrade() { + // server + // .plugin_manager + // .fire(InventoryCloseEvent::new(&self, window_type)) + // .await; + // } let player_screen_handler: Arc> = self.player_screen_handler.clone(); @@ -3026,7 +3020,7 @@ impl Player { } #[allow(clippy::too_many_lines)] - pub async fn on_slot_click(&self, packet: SClickSlot, server: &Server) { + pub async fn on_slot_click(self: &Arc, packet: SClickSlot, server: &Server) { self.update_last_action_time(); let screen_handler_arc = self.current_screen_handler.lock().await.clone(); let mut screen_handler = screen_handler_arc.lock().await; @@ -3050,7 +3044,7 @@ impl Player { return; } - if !screen_handler.can_use(self) { + if !screen_handler.can_use(self.as_ref()) { warn!( "Player {} interacted with invalid menu {:?}", self.gameprofile.name, @@ -3133,7 +3127,7 @@ impl Player { send_cancellable! {{ server; InventoryClickEvent::new( - &self.this(), + self, screen_handler.window_type(), click_type, slot, @@ -3144,7 +3138,7 @@ impl Player { ); 'after: {} 'cancelled: { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } }} @@ -3158,48 +3152,48 @@ impl Player { if is_container_slot { if !cursor_stack.is_empty() && !allow_put_items { drop(cursor_stack); - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } if cursor_stack.is_empty() && !allow_grab_items { drop(cursor_stack); - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } } SlotActionType::QuickMove => { if is_container_slot && !allow_grab_items { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } if !is_container_slot && !allow_put_items { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } SlotActionType::Swap => { if is_container_slot && (!allow_grab_items || !allow_put_items) { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } SlotActionType::Throw => { if is_container_slot && !allow_grab_items { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } SlotActionType::QuickCraft => { if !allow_put_items { // Dragging items into slots - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } SlotActionType::PickupAll => { if !allow_grab_items { - screen_handler.cancel(self).await; + screen_handler.cancel().await; return; } } @@ -3218,7 +3212,7 @@ impl Player { i32::from(slot), i32::from(packet.button), packet.mode.clone(), - self, + self.as_ref(), ) .await; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs index 0ef819b8d..394d703ab 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use wasmtime::component::Resource; use crate::plugin::loader::wasm::wasm_host::{ @@ -7,8 +8,11 @@ use crate::plugin::loader::wasm::wasm_host::{ common::{EntityPose, Position}, entity::Host, text::TextComponent, - world::{Entity, HostEntity, World}, + world::{ + BlockPos as WitBlockPos, Entity, HostEntity, RaycastResult as WitRaycastResult, World, + }, }, + wit::v0_1::world::to_wasm_block_direction, }; use pumpkin_data::entity::EntityPose as InternalEntityPose; @@ -176,6 +180,100 @@ impl HostEntity for PluginHostState { Ok(to_wasm_position(entity.get_entity().velocity.load())) } + async fn set_sneaking( + &mut self, + entity: Resource, + sneaking: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_sneaking(sneaking).await; + Ok(()) + } + + async fn set_sprinting( + &mut self, + entity: Resource, + sprinting: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_sprinting(sprinting).await; + Ok(()) + } + + async fn is_swimming(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .swimming + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn set_swimming( + &mut self, + entity: Resource, + swimming: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_swimming(swimming).await; + Ok(()) + } + + async fn set_invisible( + &mut self, + entity: Resource, + invisible: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_invisible(invisible).await; + Ok(()) + } + + async fn set_glowing( + &mut self, + entity: Resource, + glowing: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_glowing(glowing).await; + Ok(()) + } + + async fn is_fall_flying(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .fall_flying + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn set_fall_flying( + &mut self, + entity: Resource, + fall_flying: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_fall_flying(fall_flying).await; + Ok(()) + } + + async fn is_on_fire(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .has_visual_fire + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn set_on_fire( + &mut self, + entity: Resource, + on_fire: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().set_on_fire(on_fire).await; + Ok(()) + } + async fn get_pose(&mut self, entity: Resource) -> wasmtime::Result { let entity = entity_from_resource(self, &entity)?; Ok(map_entity_pose(entity.get_entity().pose.load())) @@ -295,6 +393,43 @@ impl HostEntity for PluginHostState { Ok(()) } + async fn raycast( + &mut self, + entity: Resource, + max_distance: f64, + _fluid_handling: bool, + ) -> wasmtime::Result> { + let entity = entity_from_resource(self, &entity)?; + let start = entity.get_eye_pos(); + let direction = entity.get_looking_vector(); + let end = start + direction * max_distance; + let world = entity.get_entity().world.load_full(); + + let hit = world + .raycast( + start, + end, + |pos: &pumpkin_util::math::position::BlockPos, w: &Arc| { + let pos = *pos; + let world = w.clone(); + async move { + let block = world.get_block_state(&pos).await; + !block.is_air() + } + }, + ) + .await; + + Ok(hit.map(|(pos, face)| WitRaycastResult { + pos: WitBlockPos { + x: pos.0.x, + y: pos.0.y, + z: pos.0.z, + }, + face: to_wasm_block_direction(face), + })) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { let _ = self .resource_table diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index 4b02f1cc9..47aaf7bdd 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -16,14 +16,18 @@ use crate::{ events::{ from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position, }, - pumpkin::{self, plugin::player::Player, plugin::world::World}, + pumpkin::{ + self, + plugin::player::{Player, PlayerSkin, SkinParts}, + plugin::world::World, + }, }, }, }; use pumpkin_inventory::player::player_inventory::PlayerInventory; use pumpkin_util::permission::PermissionLvl; -fn player_from_resource( +pub fn player_from_resource( state: &PluginHostState, player: &Resource, ) -> wasmtime::Result> { @@ -657,116 +661,6 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(()) } - async fn is_sneaking(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().sneaking.load(Ordering::Relaxed)) - } - - async fn set_sneaking( - &mut self, - player: Resource, - sneaking: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_sneaking(sneaking).await; - Ok(()) - } - - async fn is_sprinting(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().sprinting.load(Ordering::Relaxed)) - } - - async fn set_sprinting( - &mut self, - player: Resource, - sprinting: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_sprinting(sprinting).await; - Ok(()) - } - - async fn is_swimming(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().swimming.load(Ordering::Relaxed)) - } - - async fn set_swimming( - &mut self, - player: Resource, - swimming: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_swimming(swimming).await; - Ok(()) - } - - async fn is_invisible(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().invisible.load(Ordering::Relaxed)) - } - - async fn set_invisible( - &mut self, - player: Resource, - invisible: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_invisible(invisible).await; - Ok(()) - } - - async fn is_glowing(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().glowing.load(Ordering::Relaxed)) - } - - async fn set_glowing( - &mut self, - player: Resource, - glowing: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_glowing(glowing).await; - Ok(()) - } - - async fn is_fall_flying(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().fall_flying.load(Ordering::Relaxed)) - } - - async fn set_fall_flying( - &mut self, - player: Resource, - fall_flying: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_fall_flying(fall_flying).await; - Ok(()) - } - - async fn is_on_fire(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().has_visual_fire.load(Ordering::Relaxed)) - } - - async fn set_on_fire( - &mut self, - player: Resource, - on_fire: bool, - ) -> wasmtime::Result<()> { - let player = player_from_resource(self, &player)?; - player.get_entity().set_on_fire(on_fire).await; - Ok(()) - } - - async fn is_on_ground(&mut self, player: Resource) -> wasmtime::Result { - let player = player_from_resource(self, &player)?; - Ok(player.get_entity().on_ground.load(Ordering::Relaxed)) - } - async fn is_flying(&mut self, player: Resource) -> wasmtime::Result { let player = player_from_resource(self, &player)?; Ok(player.is_flying().await) @@ -824,6 +718,85 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(player.get_ip().await) } + async fn get_skin(&mut self, player: Resource) -> wasmtime::Result> { + let player = player_from_resource(self, &player)?; + Ok(player + .gameprofile + .properties + .iter() + .find(|p| p.name == "textures") + .map(|p| PlayerSkin { + value: p.value.clone(), + signature: p.signature.clone(), + })) + } + + async fn get_skin_parts(&mut self, player: Resource) -> wasmtime::Result { + let player = player_from_resource(self, &player)?; + let mask = player.config.load().skin_parts; + let mut parts = SkinParts::empty(); + if mask & 0x01 != 0 { + parts |= SkinParts::CAPE; + } + if mask & 0x02 != 0 { + parts |= SkinParts::JACKET; + } + if mask & 0x04 != 0 { + parts |= SkinParts::LEFT_SLEEVE; + } + if mask & 0x08 != 0 { + parts |= SkinParts::RIGHT_SLEEVE; + } + if mask & 0x10 != 0 { + parts |= SkinParts::LEFT_PANTS_LEG; + } + if mask & 0x20 != 0 { + parts |= SkinParts::RIGHT_PANTS_LEG; + } + if mask & 0x40 != 0 { + parts |= SkinParts::HAT; + } + Ok(parts) + } + + async fn set_skin_parts( + &mut self, + player: Resource, + parts: SkinParts, + ) -> wasmtime::Result<()> { + let player = player_from_resource(self, &player)?; + let mut mask = 0u8; + if parts.contains(SkinParts::CAPE) { + mask |= 0x01; + } + if parts.contains(SkinParts::JACKET) { + mask |= 0x02; + } + if parts.contains(SkinParts::LEFT_SLEEVE) { + mask |= 0x04; + } + if parts.contains(SkinParts::RIGHT_SLEEVE) { + mask |= 0x08; + } + if parts.contains(SkinParts::LEFT_PANTS_LEG) { + mask |= 0x10; + } + if parts.contains(SkinParts::RIGHT_PANTS_LEG) { + mask |= 0x20; + } + if parts.contains(SkinParts::HAT) { + mask |= 0x40; + } + + { + let mut config = (**player.config.load()).clone(); + config.skin_parts = mask; + player.config.store(Arc::new(config)); + }; + player.send_client_information().await; + Ok(()) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { let _ = self .resource_table diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs index 942466c53..b7dc2cd9e 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs @@ -2,6 +2,9 @@ use pumpkin_util::text::TextComponent; use uuid::Uuid; use wasmtime::component::Resource; +use crate::command::CommandSender; +use pumpkin::plugin::server::CommandSender as WasmCommandSender; + use super::player::text_component_from_resource; use crate::plugin::loader::wasm::wasm_host::{ state::{PluginHostState, ServerResource}, @@ -9,7 +12,7 @@ use crate::plugin::loader::wasm::wasm_host::{ self, plugin::{ player::Player, - server::{Difficulty, Server}, + server::{Difficulty, Dimension, Server}, }, }, }; @@ -156,6 +159,28 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { })) } + async fn create_world( + &mut self, + _rep: Resource, + name: String, + dimension: Dimension, + ) -> wasmtime::Result> { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + let internal_dim = match dimension { + Dimension::Overworld => pumpkin_data::dimension::Dimension::OVERWORLD, + Dimension::Nether => pumpkin_data::dimension::Dimension::THE_NETHER, + Dimension::End => pumpkin_data::dimension::Dimension::THE_END, + }; + + let world = server.create_world(name, internal_dim).await; + self.add_world(world) + .map_err(|_| wasmtime::Error::msg("failed to add world resource")) + } + async fn broadcast(&mut self, _rep: Resource, message: String) -> wasmtime::Result<()> { let server = self .server @@ -192,6 +217,39 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { Ok(()) } + async fn execute_command( + &mut self, + _rep: Resource, + command: String, + sender: WasmCommandSender, + ) -> wasmtime::Result<()> { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + let native_sender = match sender { + WasmCommandSender::Console => CommandSender::Console, + WasmCommandSender::Player(player_res) => { + // Extract the native Player reference from the WASM resource + let player_resource = + self.resource_table + .get::( + &Resource::new_own(player_res.rep()), + )?; + + CommandSender::Player(player_resource.provider.clone()) + } + }; + + let dispatcher = server.command_dispatcher.read().await; + dispatcher + .handle_command(&native_sender.into_source(server).await, &command) + .await; + + Ok(()) + } + async fn get_max_players(&mut self, _rep: Resource) -> wasmtime::Result { let server = self .server diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index 057a66550..8d81841f7 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -1,3 +1,4 @@ +use pumpkin_data::BlockDirection as InternalBlockDirection; use pumpkin_data::block_state::PistonBehavior; use pumpkin_util::math::position::BlockPos; use pumpkin_world::world::{BlockFlags, SimpleWorld}; @@ -5,8 +6,8 @@ use std::sync::Arc; use wasmtime::component::Resource; use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{ - BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockState as WitBlockState, - PistonBehavior as WitPistonBehavior, + BlockDirection as WitBlockDirection, BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, + BlockState as WitBlockState, PistonBehavior as WitPistonBehavior, }; use crate::plugin::loader::wasm::wasm_host::{ state::{PluginHostState, TextComponentResource, WorldResource}, @@ -14,6 +15,17 @@ use crate::plugin::loader::wasm::wasm_host::{ }; use crate::world::explosion::Explosion; +pub(crate) const fn to_wasm_block_direction(dir: InternalBlockDirection) -> WitBlockDirection { + match dir { + InternalBlockDirection::Down => WitBlockDirection::Down, + InternalBlockDirection::Up => WitBlockDirection::Up, + InternalBlockDirection::North => WitBlockDirection::North, + InternalBlockDirection::South => WitBlockDirection::South, + InternalBlockDirection::West => WitBlockDirection::West, + InternalBlockDirection::East => WitBlockDirection::East, + } +} + // --- Trapping Helpers --- impl PluginHostState { fn get_world_res(&self, res: &Resource) -> wasmtime::Result<&WorldResource> { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index cd64a285f..85d4cbc6e 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -341,6 +341,54 @@ impl Server { .unwrap() } + pub async fn create_world(self: &Arc, name: String, dimension: Dimension) -> Arc { + { + let worlds = self.worlds.load(); + if let Some(world) = worlds + .iter() + .find(|w| w.get_world_name() == name && w.dimension == dimension) + { + return world.clone(); + } + } + + let server = self.clone(); + let name_clone = name.clone(); + tokio::task::spawn_blocking(move || { + let world_path = server.basic_config.get_world_path().join(name_clone); + let registry = server.block_registry.clone(); + let l_info = server.level_info.clone(); + let weak = Arc::downgrade(&server); + let config = Arc::new(server.advanced_config.world.clone()); + let seed = server.level_info.load().world_gen_settings.seed; + + // TODO: gen_pool should be reused + let world = World::load( + pumpkin_world::dimension::into_level( + dimension, + &config, + world_path, + registry.clone(), + seed, + None, + ), + l_info, + dimension, + registry, + weak, + ); + let world_arc = Arc::new(world); + server.worlds.rcu(|worlds| { + let mut new_worlds = (**worlds).clone(); + new_worlds.push(world_arc.clone()); + new_worlds + }); + world_arc + }) + .await + .expect("World creation panicked") + } + /// Adds a new player to the server. /// /// This function takes an `Arc` representing the connected client and performs the following actions: @@ -430,8 +478,7 @@ impl Server { } // Wrap in Arc after data is loaded - let mut player = Arc::new(player); - Arc::get_mut(&mut player).unwrap().this = Arc::downgrade(&player); + let player = Arc::new(player); send_cancellable! {{ self; diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 71e5af385..40b256cd6 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -381,12 +381,11 @@ pub async fn spawn_category_for_position( chunk_pos: &Vector2, spawn_state: &SpawnState, ) { - // TODO StructureManager structureManager = level.structureManager(); - // TODO blockState.isRedstoneConductor(chunk, pos) is true then return let mut batch_buffer = vec![]; let mut spawn_cluster_size = 0; let mut new_pos = pos; let player_positions: Vec<_> = world.players.load().iter().map(|p| p.position()).collect(); + for _ in 0..3 { let mut new_x = new_pos.0.x; let mut new_z = new_pos.0.z; @@ -398,18 +397,27 @@ pub async fn spawn_category_for_position( new_x += rng().random_range(0..6) - rng().random_range(0..6); new_z += rng().random_range(0..6) - rng().random_range(0..6); new_pos = BlockPos::new(new_x, new_pos.0.y, new_z); - let new_pos_center = new_pos.to_centered_f64(); - let player_distance = get_nearest_player(&new_pos_center, &player_positions); + + let spawn_pos_f64 = Vector3::new( + f64::from(new_pos.0.x) + 0.5, + f64::from(new_pos.0.y), + f64::from(new_pos.0.z) + 0.5, + ); + + let player_distance = get_nearest_player(&spawn_pos_f64, &player_positions); if !is_right_distance_to_player_and_spawn_point(&new_pos, player_distance, chunk_pos) { inc += 1; continue; } + let Some(spawner) = get_random_spawn_mob_at(world, category, &new_pos).await else { break 'outer; }; + random_group_size = rng().random_range(spawner.min_count..=spawner.max_count); let entity_type = &EntityType::from_name(spawner.r#type.strip_prefix("minecraft:").unwrap()).unwrap(); + if !is_valid_spawn_position_for_type( world, &new_pos, @@ -426,21 +434,19 @@ pub async fn spawn_category_for_position( inc += 1; continue; } - let entity = from_type(entity_type, new_pos_center, world, Uuid::new_v4()).await; + + let entity = from_type(entity_type, spawn_pos_f64, world, Uuid::new_v4()).await; entity .get_entity() .set_rotation(rng().random::() * 360., 0.); - // TODO isValidPositionForMob(level, mob, f) - // TODO spawnGroupData = mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData); + spawn_cluster_size += 1; - //group_size += 1; batch_buffer.push(entity); spawn_state.after_spawn(entity_type, &new_pos, world).await; if spawn_cluster_size >= entity_type.limit_per_chunk { return; } - //TODO mob.isMaxGroupSizeReached(p) inc += 1; } } @@ -601,14 +607,19 @@ pub async fn is_spawn_position_ok( SpawnLocation::InLava => world.get_fluid(block_pos).await.has_tag(&MINECRAFT_LAVA), SpawnLocation::InWater => { // TODO !level.getBlockState(blockPos).isRedstoneConductor(level, blockPos) + let above_state = world.get_block_state(&block_pos.up()).await; world.get_fluid(block_pos).await.has_tag(&MINECRAFT_WATER) + && !above_state.is_full_cube() } SpawnLocation::OnGround => { let down = world.get_block_state(&block_pos.down()).await; let up = world.get_block_state(&block_pos.up()).await; let cur = world.get_block_state(block_pos).await; // TODO: blockState.allowsSpawning - if down.is_side_solid(BlockDirection::Up) { + let is_valid_spawn_below = + down.is_side_solid(BlockDirection::Up) && down.luminance < 14; + + if is_valid_spawn_below { is_valid_empty_spawn_block(cur) && is_valid_empty_spawn_block(up) } else { false @@ -620,10 +631,20 @@ pub async fn is_spawn_position_ok( #[must_use] pub fn is_valid_empty_spawn_block(state: &'static BlockState) -> bool { - // TODO: emitsRedstonePower - if state.is_full_cube() || state.is_liquid() { + if state.is_full_cube() { return false; } - // TODO !entityType.isBlockDangerous(blockState); - !Block::from_state_id(state.id).has_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE) + // if state.is_signal_source() { + // return false; + // } + if state.is_liquid() { + return false; + } + if Block::from_state_id(state.id).has_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE) { + return false; + } + + // TODO: !entityType.isBlockDangerous(blockState) + // (e.g., preventing spawns inside Sweet Berry Bushes, Wither Roses, or Fire) + true }