feat(plugin-api): add entity api

This commit is contained in:
Alexander Medvedev 2026-04-15 18:10:51 +02:00
parent 9012a92287
commit b31f8a0fdd
15 changed files with 547 additions and 112 deletions

View File

@ -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;

View File

@ -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<nbt-compound;
// get-inventory: func() -> option<inventory>;
// 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<property-delegate>;
// to-experience-container: func() -> option<experience-container>;
}
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),
}
}

View File

@ -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};

View File

@ -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,

View File

@ -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<nbt-compound;
// get-inventory: func() -> option<inventory>;
// 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<property-delegate>;
// to-experience-container: func() -> option<experience-container>;
}
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};
}

View File

@ -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.

View File

@ -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 ---

View File

@ -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<text-component>;
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<entity>;
}
}

View File

@ -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<Option<Mutex<PortalManager>>>,
/// Custom name for the entity
pub custom_name: Option<TextComponent>,
pub custom_name: ArcSwap<Option<TextComponent>>,
/// 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();

View File

@ -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<T> {
pub type ServerResource = WasmResource<Arc<Server>>;
pub type ContextResource = WasmResource<Arc<Context>>;
pub type PlayerResource = WasmResource<Arc<Player>>;
pub type EntityResource = WasmResource<Arc<dyn EntityBase>>;
pub type WorldResource = WasmResource<Arc<World>>;
pub type ScoreboardResource = WasmResource<Arc<World>>;
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
@ -91,6 +93,14 @@ impl PluginHostState {
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_entity<T>(
&mut self,
provider: Arc<dyn EntityBase>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(EntityResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_world<T>(
&mut self,
provider: Arc<World>,

View File

@ -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<BlockEntity>) -> wasmtime::Result<String> {
todo!("Implement resource_location lookup")
}
async fn get_position(
&mut self,
_res: Resource<BlockEntity>,
) -> wasmtime::Result<BlockPosition> {
todo!("Implement get_position lookup")
}
async fn get_id(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement get_id lookup")
}
async fn is_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement is_dirty lookup")
}
async fn clear_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!("Implement clear_dirty logic")
}
async fn drop(&mut self, _rep: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!();
}
}
impl pumpkin::plugin::block_entity::HostCommandBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
todo!("Implement upcasting/mapping to BlockEntity")
}
async fn last_output(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<String> {
todo!("Implement last_output lookup")
}
async fn track_output(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement track_output lookup")
}
async fn success_count(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement success_count lookup")
}
async fn command(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> {
todo!("Implement command lookup")
}
async fn auto(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement auto lookup")
}
async fn condition_met(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<bool> {
todo!("Implement condition_met lookup")
}
async fn powered(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement powered lookup")
}
async fn drop(&mut self, _rep: Resource<CommandBlockEntity>) -> wasmtime::Result<()> {
todo!();
}
}

View File

@ -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<BlockEntity>) -> wasmtime::Result<String> {
todo!("Implement resource_location lookup")
}
fn entity_from_resource(
state: &PluginHostState,
entity: &Resource<Entity>,
) -> wasmtime::Result<std::sync::Arc<dyn crate::entity::EntityBase>> {
state
.resource_table
.get::<EntityResource>(&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<BlockEntity>,
) -> wasmtime::Result<BlockPosition> {
todo!("Implement get_position lookup")
}
async fn get_id(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement get_id lookup")
}
async fn is_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement is_dirty lookup")
}
async fn clear_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!("Implement clear_dirty logic")
}
async fn drop(&mut self, _rep: Resource<BlockEntity>) -> 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<Entity>) -> wasmtime::Result<u32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_id as u32)
}
async fn get_uuid(&mut self, entity: Resource<Entity>) -> wasmtime::Result<String> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_uuid.to_string())
}
async fn get_type(&mut self, entity: Resource<Entity>) -> wasmtime::Result<String> {
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<Entity>) -> wasmtime::Result<Position> {
let entity = entity_from_resource(self, &entity)?;
Ok(to_wasm_position(entity.get_entity().pos.load()))
}
async fn get_world(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Resource<World>> {
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<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().yaw.load())
}
async fn get_pitch(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().pitch.load())
}
async fn get_head_yaw(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().head_yaw.load())
}
async fn is_on_ground(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
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<Entity>) -> wasmtime::Result<bool> {
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<Entity>) -> wasmtime::Result<bool> {
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<Entity>) -> wasmtime::Result<bool> {
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<Entity>) -> wasmtime::Result<bool> {
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<CommandBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
todo!("Implement upcasting/mapping to BlockEntity")
entity: Resource<Entity>,
pos: Position,
world_ref: Resource<World>,
) -> wasmtime::Result<()> {
let entity_base = entity_from_resource(self, &entity)?;
let world = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::WorldResource>(
&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<CommandBlockEntity>,
) -> wasmtime::Result<String> {
todo!("Implement last_output lookup")
entity: Resource<Entity>,
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<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement track_output lookup")
async fn get_velocity(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> {
let entity = entity_from_resource(self, &entity)?;
Ok(to_wasm_position(entity.get_entity().velocity.load()))
}
async fn success_count(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement success_count lookup")
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()))
}
async fn command(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> {
todo!("Implement command lookup")
}
async fn auto(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement auto lookup")
}
async fn condition_met(
async fn get_name(
&mut self,
_res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<bool> {
todo!("Implement condition_met lookup")
entity: Resource<Entity>,
) -> wasmtime::Result<Resource<TextComponent>> {
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<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement powered lookup")
async fn set_custom_name(
&mut self,
entity: Resource<Entity>,
name: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity_base = entity_from_resource(self, &entity)?;
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&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<CommandBlockEntity>) -> wasmtime::Result<()> {
todo!();
async fn get_custom_name(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<TextComponent>>> {
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<Entity>,
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<Entity>) -> wasmtime::Result<bool> {
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<Entity>) -> wasmtime::Result<bool> {
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<Entity>,
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<Entity>) -> wasmtime::Result<i32> {
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<Entity>,
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<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().remove().await;
Ok(())
}
async fn drop(&mut self, rep: Resource<Entity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<EntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}

View File

@ -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;

View File

@ -131,6 +131,15 @@ impl DowncastResourceExt<PlayerResource> for Resource<Player> {
impl pumpkin::plugin::player::Host for PluginHostState {}
impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn as_entity(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<Resource<pumpkin::plugin::world::Entity>> {
let player = player_from_resource(self, &player)?;
self.add_entity(player as Arc<dyn EntityBase>)
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))
}
async fn get_id(&mut self, player: Resource<Player>) -> wasmtime::Result<String> {
let player = player_from_resource(self, &player)?;
Ok(player.gameprofile.id.to_string())

View File

@ -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<World>,
) -> wasmtime::Result<Vec<Resource<pumpkin::plugin::entity::Entity>>> {
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<dyn crate::entity::EntityBase>)?);
}
// 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<World>) -> wasmtime::Result<()> {
self.resource_table
.delete::<WorldResource>(Resource::new_own(rep.rep()))