From b31f8a0fdd93e2b9b5f6aed0488dbcc1e6d5c782 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 15 Apr 2026 18:10:51 +0200 Subject: [PATCH] feat(plugin-api): add entity api --- pumpkin-plugin-api/src/lib.rs | 4 +- pumpkin-plugin-wit/v0.1.0/block-entity.wit | 36 ++ pumpkin-plugin-wit/v0.1.0/command.wit | 5 +- pumpkin-plugin-wit/v0.1.0/common.wit | 22 ++ pumpkin-plugin-wit/v0.1.0/entity.wit | 35 +- pumpkin-plugin-wit/v0.1.0/player.wit | 3 + pumpkin-plugin-wit/v0.1.0/plugin.wit | 4 + pumpkin-plugin-wit/v0.1.0/world.wit | 43 ++- pumpkin/src/entity/mod.rs | 38 +- .../src/plugin/loader/wasm/wasm_host/state.rs | 10 + .../wasm/wasm_host/wit/v0_1_0/block_entity.rs | 90 +++++ .../wasm/wasm_host/wit/v0_1_0/entity.rs | 338 ++++++++++++++---- .../loader/wasm/wasm_host/wit/v0_1_0/mod.rs | 1 + .../wasm/wasm_host/wit/v0_1_0/player.rs | 9 + .../loader/wasm/wasm_host/wit/v0_1_0/world.rs | 21 ++ 15 files changed, 547 insertions(+), 112 deletions(-) create mode 100644 pumpkin-plugin-wit/v0.1.0/block-entity.wit create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/block_entity.rs diff --git a/pumpkin-plugin-api/src/lib.rs b/pumpkin-plugin-api/src/lib.rs index f030d4a2e..d82cb99e9 100644 --- a/pumpkin-plugin-api/src/lib.rs +++ b/pumpkin-plugin-api/src/lib.rs @@ -43,9 +43,9 @@ pub mod command { } pub use wit::pumpkin::plugin::{ - command as command_wit, common, + block_entity, command as command_wit, common, context::{Context, Server}, - gui, permission, scoreboard, server, text, world, + entity, gui, permission, scoreboard, server, text, world, }; pub mod logging; diff --git a/pumpkin-plugin-wit/v0.1.0/block-entity.wit b/pumpkin-plugin-wit/v0.1.0/block-entity.wit new file mode 100644 index 000000000..c13c047b7 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/block-entity.wit @@ -0,0 +1,36 @@ +interface block-entity { + use common.{block-position}; + + resource block-entity { + resource-location: func() -> string; + get-position: func() -> block-position; + get-id: func() -> u32; + // write-nbt: func(nbt: nbt-compound); + // from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; + // tick: func(world: simple-world); + // write-internal: func(nbt: nbt-compound); + // chunk-data-nbt: func() -> option option; + // set-block-state: func(block-state: block-state-id); + // on-block-replaced: func(world: simple-world, position: block-position); + is-dirty: func() -> bool; + clear-dirty: func(); + // to-property-delegate: func() -> option; + // to-experience-container: func() -> option; + } + + resource command-block-entity { + get-block-entity: func () -> block-entity; + last-output: func() -> string; + track-output: func() -> bool; + success-count: func() -> u32; + command: func() -> string; + auto: func() -> bool; + condition-met: func() -> bool; + powered: func() -> bool; + } + + variant block-entity-type { + command-block-entity(command-block-entity), + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/command.wit b/pumpkin-plugin-wit/v0.1.0/command.wit index e1265fd28..bc6f398a5 100644 --- a/pumpkin-plugin-wit/v0.1.0/command.wit +++ b/pumpkin-plugin-wit/v0.1.0/command.wit @@ -1,7 +1,8 @@ interface command { - use player.{player}; use %world.{%world}; - use entity.{command-block-entity, entity}; + use entity.{entity}; + use player.{player}; + use block-entity.{command-block-entity}; use common.{position, block-position, locale}; use server.{server, difficulty}; use text.{text-component}; diff --git a/pumpkin-plugin-wit/v0.1.0/common.wit b/pumpkin-plugin-wit/v0.1.0/common.wit index 6eab86557..24d796edf 100644 --- a/pumpkin-plugin-wit/v0.1.0/common.wit +++ b/pumpkin-plugin-wit/v0.1.0/common.wit @@ -67,6 +67,28 @@ interface common { count: u8, } + /// Represents the various poses an entity can be in. + enum entity-pose { + standing, + fall-flying, + sleeping, + swimming, + spin-attack, + crouching, + long-jumping, + dying, + croaking, + using-tongue, + sitting, + roaring, + sniffing, + emerging, + digging, + sliding, + shooting, + inhaling, + } + /// All Minecraft-supported locales. enum locale { af-za, diff --git a/pumpkin-plugin-wit/v0.1.0/entity.wit b/pumpkin-plugin-wit/v0.1.0/entity.wit index 5f395349f..b40bebb4d 100644 --- a/pumpkin-plugin-wit/v0.1.0/entity.wit +++ b/pumpkin-plugin-wit/v0.1.0/entity.wit @@ -1,36 +1,3 @@ interface entity { - use common.{block-position}; - - resource block-entity { - resource-location: func() -> string; - get-position: func() -> block-position; - get-id: func() -> u32; - // write-nbt: func(nbt: nbt-compound); - // from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; - // tick: func(world: simple-world); - // write-internal: func(nbt: nbt-compound); - // chunk-data-nbt: func() -> option option; - // set-block-state: func(block-state: block-state-id); - // on-block-replaced: func(world: simple-world, position: block-position); - is-dirty: func() -> bool; - clear-dirty: func(); - // to-property-delegate: func() -> option; - // to-experience-container: func() -> option; - } - - resource command-block-entity { - get-block-entity: func () -> block-entity; - last-output: func() -> string; - track-output: func() -> bool; - success-count: func() -> u32; - command: func() -> string; - auto: func() -> bool; - condition-met: func() -> bool; - powered: func() -> bool; - } - - variant entity { - command-block-entity(command-block-entity), - } + use %world.{entity}; } diff --git a/pumpkin-plugin-wit/v0.1.0/player.wit b/pumpkin-plugin-wit/v0.1.0/player.wit index 48385d518..dda89dd1e 100644 --- a/pumpkin-plugin-wit/v0.1.0/player.wit +++ b/pumpkin-plugin-wit/v0.1.0/player.wit @@ -2,6 +2,7 @@ interface player { use common.{hand, item-stack, position, game-mode}; use %world.{%world}; + use entity.{entity}; use permission.{permission-level}; use text.{text-component}; use gui.{gui}; @@ -26,6 +27,8 @@ interface player { /// A handle to a player on the server. resource player { + /// Returns the player as an entity. + as-entity: func () -> entity; /// Returns the player's unique UUID as a string. get-id: func () -> string; /// Returns the player's username. diff --git a/pumpkin-plugin-wit/v0.1.0/plugin.wit b/pumpkin-plugin-wit/v0.1.0/plugin.wit index dd6a27298..07ae1a471 100644 --- a/pumpkin-plugin-wit/v0.1.0/plugin.wit +++ b/pumpkin-plugin-wit/v0.1.0/plugin.wit @@ -29,6 +29,10 @@ world plugin { import i18n; /// Provides task scheduling for delayed or repeating logic. import scheduler; + /// Provides world information and block manipulation. + import %world; + /// Provides entity types and manipulation. + import entity; // --- Plugin-provided exports --- diff --git a/pumpkin-plugin-wit/v0.1.0/world.wit b/pumpkin-plugin-wit/v0.1.0/world.wit index c1ac9c77f..a43e0b81d 100644 --- a/pumpkin-plugin-wit/v0.1.0/world.wit +++ b/pumpkin-plugin-wit/v0.1.0/world.wit @@ -1,5 +1,5 @@ interface %world { - use common.{position}; + use common.{position, entity-pose}; use text.{text-component}; use scoreboard.{scoreboard}; @@ -101,6 +101,44 @@ interface %world { z: s32, } + resource entity { + get-id: func() -> u32; + get-uuid: func() -> string; + get-type: func() -> string; + + get-position: func() -> position; + get-world: func() -> %world; + + get-yaw: func() -> f32; + get-pitch: func() -> f32; + get-head-yaw: func() -> f32; + + is-on-ground: func() -> bool; + is-sneaking: func() -> bool; + is-sprinting: func() -> bool; + is-invisible: func() -> bool; + is-glowing: func() -> bool; + + teleport: func(pos: position, world-ref: %world); + set-velocity: func(velocity: position); + get-velocity: func() -> position; + + get-pose: func() -> entity-pose; + get-name: func() -> text-component; + set-custom-name: func(name: text-component); + get-custom-name: func() -> option; + set-custom-name-visible: func(visible: bool); + is-custom-name-visible: func() -> bool; + + is-invulnerable: func() -> bool; + set-invulnerable: func(invulnerable: bool); + + get-fire-ticks: func() -> s32; + set-fire-ticks: func(ticks: s32); + + remove: func(); + } + /// Represents a Minecraft world (dimension). resource %world { /// Returns the unique identifier of this world. @@ -165,5 +203,8 @@ interface %world { /// Returns the minimum Y coordinate (bottom of the world). get-min-y: func() -> s32; + + /// Returns a list of all entities in this world. + get-entities: func() -> list; } } diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index ca66795b7..e6e66e4c4 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -304,6 +304,8 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any { let entity = self.get_entity(); entity .custom_name + .load() + .as_ref() .clone() .unwrap_or(TextComponent::translate( format!("entity.minecraft.{}", entity.entity_type.resource_name), @@ -315,13 +317,16 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any { Box::pin(async move { // TODO: team color let entity = self.get_entity(); - let mut name = entity - .custom_name - .clone() - .unwrap_or(TextComponent::translate( - format!("entity.minecraft.{}", entity.entity_type.resource_name), - [], - )); + let mut name = + entity + .custom_name + .load() + .as_ref() + .clone() + .unwrap_or(TextComponent::translate( + format!("entity.minecraft.{}", entity.entity_type.resource_name), + [], + )); let name_clone = name.clone(); name = name.hover_event(HoverEvent::show_entity( entity.entity_uuid.to_string(), @@ -474,9 +479,9 @@ pub struct Entity { pub portal_manager: Mutex>>, /// Custom name for the entity - pub custom_name: Option, + pub custom_name: ArcSwap>, /// Indicates whether the entity's custom name is visible - pub custom_name_visible: bool, + pub custom_name_visible: AtomicBool, /// The data send in the Entity Spawn packet pub data: AtomicI32, /// Stores entity boolean flags (on fire, sneaking, invisible, glowing, etc.) @@ -600,8 +605,8 @@ impl Entity { age: AtomicI32::new(0), portal_cooldown: AtomicU32::new(0), portal_manager: Mutex::new(None), - custom_name: None, - custom_name_visible: false, + custom_name: ArcSwap::new(Arc::new(None)), + custom_name_visible: AtomicBool::new(false), no_clip: AtomicBool::new(false), movement_multiplier: AtomicCell::new(Vector3::default()), velocity_dirty: AtomicBool::new(true), @@ -636,6 +641,7 @@ impl Entity { /// Sets a custom name for the entity, typically used with nametags pub async fn set_custom_name(&self, name: TextComponent) { + self.custom_name.store(Arc::new(Some(name.clone()))); self.send_meta_data(&[Metadata::new( TrackedData::CUSTOM_NAME, MetaDataType::OPTIONAL_TEXT_COMPONENT, @@ -644,6 +650,16 @@ impl Entity { .await; } + pub async fn set_custom_name_visible(&self, visible: bool) { + self.custom_name_visible.store(visible, Ordering::Relaxed); + self.send_meta_data(&[Metadata::new( + TrackedData::CUSTOM_NAME_VISIBLE, + MetaDataType::BOOLEAN, + visible, + )]) + .await; + } + pub async fn send_velocity(&self) { let velocity = self.velocity.load(); let chunk_pos = self.chunk_pos.load(); diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index eea51751b..e02635110 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -14,6 +14,7 @@ use crate::{ args::ConsumedArgs, tree::{CommandTree, builder::NonLeafNodeBuilder}, }, + entity::EntityBase, entity::player::Player, plugin::{ Context, @@ -31,6 +32,7 @@ pub struct WasmResource { pub type ServerResource = WasmResource>; pub type ContextResource = WasmResource>; pub type PlayerResource = WasmResource>; +pub type EntityResource = WasmResource>; pub type WorldResource = WasmResource>; pub type ScoreboardResource = WasmResource>; pub type GuiResource = WasmResource>>; @@ -91,6 +93,14 @@ impl PluginHostState { Ok(wasmtime::component::Resource::new_own(resource.rep())) } + pub fn add_entity( + &mut self, + provider: Arc, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(EntityResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + pub fn add_world( &mut self, provider: Arc, diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/block_entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/block_entity.rs new file mode 100644 index 000000000..da4be8891 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/block_entity.rs @@ -0,0 +1,90 @@ +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::PluginHostState, + wit::v0_1_0::pumpkin::{ + self, + plugin::{ + block_entity::{BlockEntity, CommandBlockEntity}, + common::BlockPosition, + }, + }, +}; + +impl pumpkin::plugin::block_entity::Host for PluginHostState {} + +impl pumpkin::plugin::block_entity::HostBlockEntity for PluginHostState { + async fn resource_location(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement resource_location lookup") + } + + async fn get_position( + &mut self, + _res: Resource, + ) -> wasmtime::Result { + todo!("Implement get_position lookup") + } + + async fn get_id(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement get_id lookup") + } + + async fn is_dirty(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement is_dirty lookup") + } + + async fn clear_dirty(&mut self, _res: Resource) -> wasmtime::Result<()> { + todo!("Implement clear_dirty logic") + } + + async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { + todo!(); + } +} + +impl pumpkin::plugin::block_entity::HostCommandBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + _res: Resource, + ) -> wasmtime::Result> { + todo!("Implement upcasting/mapping to BlockEntity") + } + + async fn last_output( + &mut self, + _res: Resource, + ) -> wasmtime::Result { + todo!("Implement last_output lookup") + } + + async fn track_output(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement track_output lookup") + } + + async fn success_count(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement success_count lookup") + } + + async fn command(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement command lookup") + } + + async fn auto(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement auto lookup") + } + + async fn condition_met( + &mut self, + _res: Resource, + ) -> wasmtime::Result { + todo!("Implement condition_met lookup") + } + + async fn powered(&mut self, _res: Resource) -> wasmtime::Result { + todo!("Implement powered lookup") + } + + async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { + todo!(); + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs index 1125b4818..70aa0217e 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs @@ -1,90 +1,304 @@ use wasmtime::component::Resource; use crate::plugin::loader::wasm::wasm_host::{ - state::PluginHostState, - wit::v0_1_0::pumpkin::{ - self, - plugin::{ - common::BlockPosition, - entity::{BlockEntity, CommandBlockEntity}, - }, + state::{EntityResource, PluginHostState}, + wit::v0_1_0::events::to_wasm_position, + wit::v0_1_0::pumpkin::plugin::{ + common::{EntityPose, Position}, + entity::Host, + text::TextComponent, + world::{Entity, HostEntity, World}, }, }; +use pumpkin_data::entity::EntityPose as InternalEntityPose; -impl pumpkin::plugin::entity::Host for PluginHostState {} +impl Host for PluginHostState {} -impl pumpkin::plugin::entity::HostBlockEntity for PluginHostState { - async fn resource_location(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement resource_location lookup") - } +fn entity_from_resource( + state: &PluginHostState, + entity: &Resource, +) -> wasmtime::Result> { + state + .resource_table + .get::(&Resource::new_own(entity.rep())) + .map_err(|_| wasmtime::Error::msg("invalid entity resource handle")) + .map(|resource| resource.provider.clone()) +} - async fn get_position( - &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement get_position lookup") - } - - async fn get_id(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement get_id lookup") - } - - async fn is_dirty(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement is_dirty lookup") - } - - async fn clear_dirty(&mut self, _res: Resource) -> wasmtime::Result<()> { - todo!("Implement clear_dirty logic") - } - - async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { - todo!(); +const fn map_entity_pose(pose: InternalEntityPose) -> EntityPose { + match pose { + InternalEntityPose::Standing => EntityPose::Standing, + InternalEntityPose::FallFlying => EntityPose::FallFlying, + InternalEntityPose::Sleeping => EntityPose::Sleeping, + InternalEntityPose::Swimming => EntityPose::Swimming, + InternalEntityPose::SpinAttack => EntityPose::SpinAttack, + InternalEntityPose::Crouching => EntityPose::Crouching, + InternalEntityPose::LongJumping => EntityPose::LongJumping, + InternalEntityPose::Dying => EntityPose::Dying, + InternalEntityPose::Croaking => EntityPose::Croaking, + InternalEntityPose::UsingTongue => EntityPose::UsingTongue, + InternalEntityPose::Sitting => EntityPose::Sitting, + InternalEntityPose::Roaring => EntityPose::Roaring, + InternalEntityPose::Sniffing => EntityPose::Sniffing, + InternalEntityPose::Emerging => EntityPose::Emerging, + InternalEntityPose::Digging => EntityPose::Digging, + InternalEntityPose::Sliding => EntityPose::Sliding, + InternalEntityPose::Shooting => EntityPose::Shooting, + InternalEntityPose::Inhaling => EntityPose::Inhaling, } } -impl pumpkin::plugin::entity::HostCommandBlockEntity for PluginHostState { - async fn get_block_entity( +impl HostEntity for PluginHostState { + async fn get_id(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().entity_id as u32) + } + + async fn get_uuid(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().entity_uuid.to_string()) + } + + async fn get_type(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().entity_type.resource_name.to_string()) + } + + async fn get_position(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(to_wasm_position(entity.get_entity().pos.load())) + } + + async fn get_world(&mut self, entity: Resource) -> wasmtime::Result> { + let entity = entity_from_resource(self, &entity)?; + let world = entity.get_entity().world.load_full(); + self.add_world(world) + .map_err(|_| wasmtime::Error::msg("failed to add world resource")) + } + + async fn get_yaw(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().yaw.load()) + } + + async fn get_pitch(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().pitch.load()) + } + + async fn get_head_yaw(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().head_yaw.load()) + } + + async fn is_on_ground(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .on_ground + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn is_sneaking(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .sneaking + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn is_sprinting(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .sprinting + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn is_invisible(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .invisible + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn is_glowing(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .glowing + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn teleport( &mut self, - _res: Resource, - ) -> wasmtime::Result> { - todo!("Implement upcasting/mapping to BlockEntity") + entity: Resource, + pos: Position, + world_ref: Resource, + ) -> wasmtime::Result<()> { + let entity_base = entity_from_resource(self, &entity)?; + let world = self + .resource_table + .get::( + &Resource::new_own(world_ref.rep()), + ) + .map_err(|_| wasmtime::Error::msg("invalid world resource handle"))?; + let world = world.provider.clone(); + entity_base + .teleport( + pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2), + None, + None, + world, + ) + .await; + Ok(()) } - async fn last_output( + async fn set_velocity( &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement last_output lookup") + entity: Resource, + velocity: Position, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity + .get_entity() + .velocity + .store(pumpkin_util::math::vector3::Vector3::new( + velocity.0, velocity.1, velocity.2, + )); + Ok(()) } - async fn track_output(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement track_output lookup") + async fn get_velocity(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(to_wasm_position(entity.get_entity().velocity.load())) } - async fn success_count(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement success_count lookup") + 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())) } - async fn command(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement command lookup") - } - - async fn auto(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement auto lookup") - } - - async fn condition_met( + async fn get_name( &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement condition_met lookup") + entity: Resource, + ) -> wasmtime::Result> { + let entity = entity_from_resource(self, &entity)?; + let name = entity.get_name(); + self.add_text_component(name) + .map_err(|_| wasmtime::Error::msg("failed to add text component resource")) } - async fn powered(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement powered lookup") + async fn set_custom_name( + &mut self, + entity: Resource, + name: Resource, + ) -> wasmtime::Result<()> { + let entity_base = entity_from_resource(self, &entity)?; + let text_res = self + .resource_table + .get::( + &Resource::new_own(name.rep()), + ) + .map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?; + let text = text_res.provider.clone(); + entity_base + .get_entity() + .custom_name + .store(std::sync::Arc::new(Some(text))); + Ok(()) } - async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { - todo!(); + async fn get_custom_name( + &mut self, + entity: Resource, + ) -> wasmtime::Result>> { + let entity = entity_from_resource(self, &entity)?; + let name = entity.get_entity().custom_name.load(); + if let Some(name) = name.as_ref() { + Ok(Some(self.add_text_component(name.clone()).map_err( + |_| wasmtime::Error::msg("failed to add text component resource"), + )?)) + } else { + Ok(None) + } + } + + async fn set_custom_name_visible( + &mut self, + entity: Resource, + visible: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity + .get_entity() + .custom_name_visible + .store(visible, std::sync::atomic::Ordering::Relaxed); + Ok(()) + } + + async fn is_custom_name_visible(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .custom_name_visible + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn is_invulnerable(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .invulnerable + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn set_invulnerable( + &mut self, + entity: Resource, + invulnerable: bool, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity + .get_entity() + .invulnerable + .store(invulnerable, std::sync::atomic::Ordering::Relaxed); + Ok(()) + } + + async fn get_fire_ticks(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity + .get_entity() + .fire_ticks + .load(std::sync::atomic::Ordering::Relaxed)) + } + + async fn set_fire_ticks( + &mut self, + entity: Resource, + ticks: i32, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity + .get_entity() + .fire_ticks + .store(ticks, std::sync::atomic::Ordering::Relaxed); + Ok(()) + } + + async fn remove(&mut self, entity: Resource) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + entity.get_entity().remove().await; + Ok(()) + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs index ca0862e46..c408b3de9 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs @@ -6,6 +6,7 @@ use tokio::sync::Mutex; use wasmtime::component::{Component, HasData, Linker, bindgen}; use wasmtime::{Engine, Store}; +pub mod block_entity; pub mod commands; pub mod common; pub mod context; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs index 851aeeaf1..a53408a67 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs @@ -131,6 +131,15 @@ impl DowncastResourceExt for Resource { impl pumpkin::plugin::player::Host for PluginHostState {} impl pumpkin::plugin::player::HostPlayer for PluginHostState { + async fn as_entity( + &mut self, + player: Resource, + ) -> wasmtime::Result> { + let player = player_from_resource(self, &player)?; + self.add_entity(player as Arc) + .map_err(|_| wasmtime::Error::msg("failed to add entity resource")) + } + async fn get_id(&mut self, player: Resource) -> wasmtime::Result { let player = player_from_resource(self, &player)?; Ok(player.gameprofile.id.to_string()) diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs index 7d15bc0c4..93806c809 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs @@ -1,6 +1,7 @@ use pumpkin_data::block_state::PistonBehavior; use pumpkin_util::math::position::BlockPos; use pumpkin_world::world::{BlockFlags, SimpleWorld}; +use std::sync::Arc; use wasmtime::component::Resource; use crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::world::{ @@ -343,6 +344,26 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { Ok(self.get_world_res(&world)?.provider.min_y) } + async fn get_entities( + &mut self, + world: Resource, + ) -> wasmtime::Result>> { + let world_provider = self.get_world_res(&world)?.provider.clone(); + let mut entities = Vec::new(); + + // Add players as entities + for player in world_provider.players.load().iter() { + entities.push(self.add_entity(player.clone() as Arc)?); + } + + // Add other entities + for entity in world_provider.entities.load().iter() { + entities.push(self.add_entity(entity.clone())?); + } + + Ok(entities) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { self.resource_table .delete::(Resource::new_own(rep.rep()))