mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
fix: entity spawn height
This commit is contained in:
@@ -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;
|
||||
})
|
||||
|
||||
@@ -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<string>,
|
||||
}
|
||||
|
||||
/// 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<player-skin>;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<raycast-result>;
|
||||
}
|
||||
|
||||
/// 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).
|
||||
|
||||
@@ -106,6 +106,21 @@ impl<T> Vector3<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector3<f64> {
|
||||
#[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<T: Math + PartialOrd + Copy> Vector3<T> {
|
||||
/// Calculates the squared length (magnitude) of the vector.
|
||||
///
|
||||
|
||||
@@ -136,6 +136,15 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_eye_pos(&self) -> Vector3<f64> {
|
||||
self.get_entity().get_eye_pos()
|
||||
}
|
||||
|
||||
fn get_looking_vector(&self) -> Vector3<f64> {
|
||||
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();
|
||||
|
||||
@@ -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<Self>,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn this(&self) -> Arc<Self> {
|
||||
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<Mutex<dyn ScreenHandler>> =
|
||||
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<Self>, 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;
|
||||
|
||||
|
||||
@@ -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<Entity>,
|
||||
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<Entity>,
|
||||
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<Entity>) -> wasmtime::Result<bool> {
|
||||
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<Entity>,
|
||||
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<Entity>,
|
||||
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<Entity>,
|
||||
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<Entity>) -> wasmtime::Result<bool> {
|
||||
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<Entity>,
|
||||
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<Entity>) -> wasmtime::Result<bool> {
|
||||
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<Entity>,
|
||||
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<Entity>) -> wasmtime::Result<EntityPose> {
|
||||
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<Entity>,
|
||||
max_distance: f64,
|
||||
_fluid_handling: bool,
|
||||
) -> wasmtime::Result<Option<WitRaycastResult>> {
|
||||
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<crate::world::World>| {
|
||||
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<Entity>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
|
||||
@@ -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<Player>,
|
||||
) -> wasmtime::Result<std::sync::Arc<crate::entity::player::Player>> {
|
||||
@@ -657,116 +661,6 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_sneaking(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().sneaking.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_sneaking(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().sprinting.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_sprinting(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().swimming.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_swimming(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().invisible.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_invisible(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().glowing.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_glowing(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
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<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
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<Player>,
|
||||
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<Player>) -> wasmtime::Result<bool> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().on_ground.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn is_flying(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
|
||||
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<Player>) -> wasmtime::Result<Option<PlayerSkin>> {
|
||||
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<Player>) -> wasmtime::Result<SkinParts> {
|
||||
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<Player>,
|
||||
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<Player>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
|
||||
@@ -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<Server>,
|
||||
name: String,
|
||||
dimension: Dimension,
|
||||
) -> wasmtime::Result<Resource<pumpkin::plugin::world::World>> {
|
||||
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<Server>, 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<Server>,
|
||||
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::<crate::plugin::loader::wasm::wasm_host::state::PlayerResource>(
|
||||
&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<Server>) -> wasmtime::Result<u32> {
|
||||
let server = self
|
||||
.server
|
||||
|
||||
@@ -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<World>) -> wasmtime::Result<&WorldResource> {
|
||||
|
||||
@@ -341,6 +341,54 @@ impl Server {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn create_world(self: &Arc<Self>, name: String, dimension: Dimension) -> Arc<World> {
|
||||
{
|
||||
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<Client>` 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;
|
||||
|
||||
@@ -381,12 +381,11 @@ pub async fn spawn_category_for_position(
|
||||
chunk_pos: &Vector2<i32>,
|
||||
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::<f32>() * 360., 0.);
|
||||
// TODO isValidPositionForMob(level, mob, f)
|
||||
// TODO spawnGroupData = mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData);
|
||||
|
||||
spawn_cluster_size += 1;
|
||||
//group_size += 1;
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user