feat: some more plugin api functions

This commit is contained in:
Alexander Medvedev 2026-08-04 20:54:55 +02:00
parent 460cb9b01c
commit 7c3177b404
50 changed files with 2288 additions and 4 deletions

View File

@ -421,6 +421,12 @@ fn convert_value(
} else {
format!("vec_{}", tmp)
}
} else if is_ref {
prep.push_str(&format!(
"{}let vec_{}: Vec<_> = {}.iter().map(|v| *v as _).collect();\n",
prep_prefix, tmp, src
));
format!("&vec_{}", tmp)
} else {
format!("{}.iter().map(|v| *v as _).collect()", src)
}

View File

@ -294,6 +294,11 @@ pub mod fuels;
#[path = "generated/effect.rs"]
pub mod effect;
#[cfg(feature = "effect")]
#[rustfmt::skip]
#[path = "generated/status_effect.rs"]
pub mod status_effect;
#[cfg(feature = "structures")]
#[rustfmt::skip]
#[path = "generated/structures.rs"]

View File

@ -0,0 +1,39 @@
use crate::wit::pumpkin::plugin::context::Server;
use crate::wit::pumpkin::plugin::world::Entity;
use std::collections::BTreeMap;
use std::sync::Mutex;
#[allow(unused_variables)]
pub trait AiGoal: Send + Sync {
fn can_start(&mut self, server: Server, entity: Entity) -> bool {
false
}
fn should_continue(&mut self, server: Server, entity: Entity) -> bool {
false
}
fn start(&mut self, server: Server, entity: Entity) {}
fn tick(&mut self, server: Server, entity: Entity) {}
fn stop(&mut self, server: Server, entity: Entity) {}
}
pub(crate) static AI_GOAL_HANDLERS: Mutex<LazyAiGoalHandlers> = Mutex::new(LazyAiGoalHandlers {
handlers: BTreeMap::new(),
next_id: 0,
});
#[allow(dead_code)]
pub(crate) struct LazyAiGoalHandlers {
pub handlers: BTreeMap<u32, Box<dyn AiGoal>>,
pub next_id: u32,
}
#[allow(dead_code)]
impl LazyAiGoalHandlers {
#[must_use]
pub fn register(&mut self, goal: Box<dyn AiGoal>) -> u32 {
let id = self.next_id;
self.next_id += 1;
self.handlers.insert(id, goal);
id
}
}

View File

@ -165,6 +165,49 @@ impl wit::Guest for Component {
let mut handlers = TASK_HANDLERS.lock().unwrap();
handlers.handle(handler_id, server);
}
fn handle_ai_goal_can_start(goal_id: u32, server: Server, entity: entity::Entity) -> bool {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.can_start(server, entity)
} else {
false
}
}
fn handle_ai_goal_should_continue(
goal_id: u32,
server: Server,
entity: entity::Entity,
) -> bool {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.should_continue(server, entity)
} else {
false
}
}
fn handle_ai_goal_start(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.start(server, entity);
}
}
fn handle_ai_goal_tick(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.tick(server, entity);
}
}
fn handle_ai_goal_stop(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.stop(server, entity);
}
}
}
/// Convenience alias for `core::result::Result<T, String>` used throughout the plugin API.
@ -237,3 +280,4 @@ macro_rules! register_plugin {
}
};
}
pub mod ai;

@ -1 +1 @@
Subproject commit ff6acb80b159b567c087a835f53f205f6755e403
Subproject commit ab193d1160b90c446fb35d48e6c636d8f867c473

View File

@ -51,6 +51,17 @@ impl GoalSelector {
}
}
pub fn clear(&mut self) -> Vec<PrioritizedGoal> {
let mut running = Vec::new();
for goal in self.goals.drain(..) {
if goal.running {
running.push(goal);
}
}
self.goals_by_control = [usize::MAX; 4];
running
}
fn uses_any(prioritized_goal: &PrioritizedGoal, controls: Controls) -> bool {
let goal_controls = prioritized_goal.controls();
for control in Controls::ITER {

View File

@ -71,6 +71,20 @@ impl BreathManager {
let new_air = (prev - AIR_DEPLETION_RATE).max(0);
if new_air != prev {
self.air_supply.store(new_air, Ordering::Relaxed);
if let Some(server) = player.world().server.upgrade() {
let mut event = crate::plugin::api::events::entity::entity_air_change::EntityAirChangeEvent::new(
player.entity_id(),
new_air,
);
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current()
.block_on(server.plugin_manager.fire(&server, &mut event));
});
if event.cancelled {
self.air_supply.store(prev, Ordering::Relaxed);
return;
}
}
self.send_air_supply(player);
}

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use super::{EntityBase, NBTStorage, NBTStorageInit, player::Player};
use super::{NBTStorage, NBTStorageInit, player::Player};
use crate::entity::NbtFuture;
use crossbeam::atomic::AtomicCell;
use pumpkin_data::damage::DamageType;

View File

@ -216,6 +216,22 @@ impl EndermanEntity {
world.play_sound(Sound::EntityEndermanTeleport, SoundCategory::Hostile, pos);
}
if let Some(server) = world.server.upgrade() {
let mut event =
crate::plugin::api::events::entity::entity_teleport::EntityTeleportEvent::new(
entity.entity_id,
origin,
new_pos,
);
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current()
.block_on(server.plugin_manager.fire(&server, &mut event));
});
if event.cancelled {
return false;
}
}
entity.set_pos(new_pos);
let chunk_pos = entity.chunk_pos.load();
world.broadcast_to_chunk(

View File

@ -167,6 +167,45 @@ impl MobEntity {
(self.mob_flags.load(Relaxed) & Self::AI_DISABLED_FLAG) != 0
}
pub async fn clear_ai_goals(&self, mob: &dyn Mob) {
let running_goals = self.goals_selector.lock().unwrap().clear();
for mut goal in running_goals {
goal.goal.stop(mob).await;
}
let running_target_goals = self.target_selector.lock().unwrap().clear();
for mut goal in running_target_goals {
goal.goal.stop(mob).await;
}
}
pub fn add_goal<G: crate::entity::ai::goal::Goal + 'static>(&self, priority: u8, goal: G) {
self.goals_selector
.lock()
.unwrap()
.add_goal(priority, Box::new(goal));
}
pub fn add_target_goal<G: crate::entity::ai::goal::Goal + 'static>(
&self,
priority: u8,
goal: G,
) {
self.target_selector
.lock()
.unwrap()
.add_goal(priority, Box::new(goal));
}
pub async fn set_target(&self, target: Option<Arc<dyn EntityBase>>) {
let mut t = self.target.lock().await;
*t = target;
}
pub async fn get_target(&self) -> Option<Arc<dyn EntityBase>> {
self.target.lock().await.clone()
}
fn set_mob_flag(&self, flag: u8, value: bool) {
let old_b = self.mob_flags.load(Ordering::Relaxed);
@ -560,6 +599,10 @@ pub trait Mob: EntityBase + Send + Sync {
fn mob_set_variant_name(&self, _name: &str) {}
}
impl<T: Mob + Send + 'static> EntityBase for T {
fn get_mob(&self) -> Option<&dyn Mob> {
Some(self)
}
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
self.mob_init_data_tracker().await;

View File

@ -226,6 +226,10 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
0.0
}
fn get_mob(&self) -> Option<&dyn mob::Mob> {
None
}
fn tick_in_void<'a>(&'a self, _dyn_self: &'a dyn EntityBase) -> EntityBaseFuture<'a, ()> {
Box::pin(async move { self.get_entity().remove().await })
}
@ -805,6 +809,8 @@ pub struct Entity {
pub custom_name: ArcSwap<Option<TextComponent>>,
/// Indicates whether the entity's custom name is visible
pub custom_name_visible: AtomicBool,
pub silent: AtomicBool,
pub has_no_gravity: AtomicBool,
/// Scoreboard tags attached to this entity, managed with `/tag`.
/// Vanilla allows at most [`MAX_SCOREBOARD_TAGS`] tags per entity.
pub scoreboard_tags: Mutex<HashSet<String>>,
@ -943,6 +949,8 @@ impl Entity {
portal_manager: Mutex::new(None),
custom_name: ArcSwap::new(Arc::new(None)),
custom_name_visible: AtomicBool::new(false),
silent: AtomicBool::new(false),
has_no_gravity: AtomicBool::new(false),
scoreboard_tags: Mutex::new(HashSet::new()),
no_clip: AtomicBool::new(false),
movement_multiplier: AtomicCell::new(Vector3::default()),
@ -1102,6 +1110,38 @@ impl Entity {
);
}
pub fn is_silent(&self) -> bool {
self.silent.load(Ordering::Relaxed)
}
pub fn set_silent(&self, silent: bool) {
self.silent.store(silent, Ordering::Relaxed);
self.send_meta_data(
&[Metadata::new(
TrackedData::SILENT,
MetaDataType::BOOLEAN,
silent,
)],
None,
);
}
pub fn has_no_gravity(&self) -> bool {
self.has_no_gravity.load(Ordering::Relaxed)
}
pub fn set_has_no_gravity(&self, no_gravity: bool) {
self.has_no_gravity.store(no_gravity, Ordering::Relaxed);
self.send_meta_data(
&[Metadata::new(
TrackedData::NO_GRAVITY,
MetaDataType::BOOLEAN,
no_gravity,
)],
None,
);
}
pub fn send_velocity(&self) {
let velocity = self.velocity.load();
let chunk_pos = self.chunk_pos.load();

View File

@ -418,6 +418,12 @@ pub struct ItemCooldown {
pub duration: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlayerWeather {
Clear,
Downfall,
}
pub struct Player {
/// The underlying living entity object that represents the player.
pub living_entity: LivingEntity,
@ -520,6 +526,11 @@ pub struct Player {
pub tab_list_order: AtomicI32,
pub tab_list_latency: AtomicI32,
pub tab_list_listed: AtomicBool,
pub per_player_time: AtomicCell<Option<(u64, bool)>>,
pub per_player_weather: AtomicCell<Option<PlayerWeather>>,
pub compass_target: AtomicCell<Option<pumpkin_util::math::position::BlockPos>>,
pub respawn_location: AtomicCell<Option<pumpkin_util::math::position::BlockPos>>,
pub hidden_players: Mutex<std::collections::HashSet<uuid::Uuid>>,
pub advancements: Arc<Mutex<PlayerAdvancement>>,
pub enchantment_seed: AtomicI32,
pub fishing_bobber: AtomicI32,
@ -753,11 +764,26 @@ impl Player {
tab_list_order: AtomicI32::new(0),
tab_list_latency: AtomicI32::new(0),
tab_list_listed: AtomicBool::new(true),
per_player_time: AtomicCell::new(None),
per_player_weather: AtomicCell::new(None),
compass_target: AtomicCell::new(None),
respawn_location: AtomicCell::new(None),
hidden_players: Mutex::new(std::collections::HashSet::new()),
fishing_bobber: AtomicI32::new(-1),
bedrock_skin: ArcSwap::new(Arc::new(bedrock_skin)),
}
}
/// Sets the tab list header and footer for Java Edition clients.
///
/// Note: Tab list header and footer formatting is a Java Edition-specific protocol feature
/// and is safely ignored on Bedrock Edition clients.
pub async fn set_tab_list(&self, tab_list: impl Into<crate::plugin::api::tab_list::TabList>) {
let list = tab_list.into();
self.set_tab_list_header_footer(list.header, list.footer)
.await;
}
pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) {
*self.tab_list_header.lock().await = header.clone();
*self.tab_list_footer.lock().await = footer.clone();
@ -1279,6 +1305,15 @@ impl Player {
// Send the break status before clearing the slot so the client can
// use the item texture for break particles.
if result == pumpkin_data::item_stack::DamageResult::Broken {
if let Some(server) = self.world().server.upgrade()
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
{
let mut event = crate::plugin::api::events::player::player_item_break::PlayerItemBreakEvent::new(
player_arc,
updated_stack.item.registry_key.to_string(),
);
server.plugin_manager.fire(&server, &mut event).await;
}
self.increment_stat(
statistics::StatisticCategory::Broken,
updated_stack.item.id as i32,
@ -2513,6 +2548,23 @@ impl Player {
/// Sends the world time to only this player.
pub async fn send_time(&self, world: &World) {
if let Some((custom_time, relative)) = self.per_player_time.load() {
let time_of_day = if relative {
let l_world = world.level_time.lock().await;
(l_world.time_of_day as u64 + custom_time) as i64
} else {
custom_time as i64
};
let l_world = world.level_time.lock().await;
self.client
.enqueue_packet_editioned(
&CUpdateTime::new(l_world.world_age, time_of_day, true),
&CSetTime::new(time_of_day as _),
)
.await;
return;
}
let l_world = world.level_time.lock().await;
self.client
.enqueue_packet_editioned(
@ -2522,6 +2574,94 @@ impl Player {
.await;
}
pub async fn set_player_time(&self, world: &World, time: u64, relative: bool) {
self.per_player_time.store(Some((time, relative)));
self.send_time(world).await;
}
pub async fn reset_player_time(&self, world: &World) {
self.per_player_time.store(None);
self.send_time(world).await;
}
pub fn get_player_time(&self) -> Option<u64> {
self.per_player_time.load().map(|(t, _)| t)
}
pub fn is_player_time_relative(&self) -> bool {
self.per_player_time.load().is_none_or(|(_, r)| r)
}
pub fn set_player_weather(&self, weather: PlayerWeather) {
self.per_player_weather.store(Some(weather));
}
pub fn reset_player_weather(&self) {
self.per_player_weather.store(None);
}
pub fn get_player_weather(&self) -> Option<PlayerWeather> {
self.per_player_weather.load()
}
pub async fn set_compass_target(&self, pos: pumpkin_util::math::position::BlockPos) {
use pumpkin_protocol::java::client::play::CPlayerSpawnPosition;
self.compass_target.store(Some(pos));
self.client
.enqueue_packet(&CPlayerSpawnPosition::new(pos, 0.0, 0.0, String::new()))
.await;
}
pub fn get_compass_target(&self) -> Option<pumpkin_util::math::position::BlockPos> {
self.compass_target.load()
}
pub fn set_respawn_location(&self, pos: pumpkin_util::math::position::BlockPos) {
self.respawn_location.store(Some(pos));
}
pub fn get_respawn_location(&self) -> Option<pumpkin_util::math::position::BlockPos> {
self.respawn_location.load()
}
pub async fn hide_player(&self, other_id: uuid::Uuid) {
self.hidden_players.lock().await.insert(other_id);
}
pub async fn show_player(&self, other_id: uuid::Uuid) {
self.hidden_players.lock().await.remove(&other_id);
}
pub async fn can_see(&self, other_id: &uuid::Uuid) -> bool {
!self.hidden_players.lock().await.contains(other_id)
}
pub async fn get_target_block(
&self,
world: &Arc<World>,
max_distance: f64,
) -> Option<pumpkin_util::math::position::BlockPos> {
let (yaw, pitch) = (
self.living_entity.entity.yaw.load(),
self.living_entity.entity.pitch.load(),
);
let eye_pos = self.living_entity.entity.get_eye_pos();
let yaw_rad = f64::from(yaw + 90.0).to_radians();
let pitch_rad = f64::from(-pitch).to_radians();
let dir = pumpkin_util::math::vector3::Vector3::new(
pitch_rad.cos() * yaw_rad.cos(),
pitch_rad.sin(),
pitch_rad.cos() * yaw_rad.sin(),
);
let end_pos = eye_pos + dir * max_distance;
let res = world
.raycast(eye_pos, end_pos, async |pos, w| {
!w.get_block_state(pos).is_air()
})
.await;
res.map(|(pos, _)| pos)
}
pub async fn unload_watched_chunks(&self, world: &World) {
let radial_chunks = self.watched_section.load().all_chunks_within();
let level = &world.level;
@ -2723,6 +2863,18 @@ impl Player {
}
pub async fn kick(&self, reason: DisconnectReason, message: TextComponent) {
if let Some(server) = self.world().server.upgrade()
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
{
let mut event = crate::plugin::api::events::player::player_kick::PlayerKickEvent::new(
player_arc,
message.clone().to_pretty_console(),
);
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return;
}
}
self.client.kick(reason, message).await;
}
@ -2749,6 +2901,30 @@ impl Player {
self.send_health().await;
}
pub async fn damage(
&self,
caller: &dyn crate::entity::EntityBase,
amount: f32,
damage_type: pumpkin_data::damage::DamageType,
) -> bool {
self.living_entity.damage(caller, amount, damage_type).await
}
pub async fn damage_generic(&self, amount: f32) -> bool {
use pumpkin_data::damage::DamageType;
self.living_entity
.damage(self, amount, DamageType::GENERIC)
.await
}
pub async fn kill(&self) {
use pumpkin_data::damage::DamageType;
let health = self.living_entity.health.load();
self.living_entity
.damage(self, health + 10.0, DamageType::OUT_OF_WORLD)
.await;
}
pub async fn send_health(&self) {
if !self.has_client_loaded() {
return;
@ -2800,16 +2976,49 @@ impl Player {
self.send_health().await;
}
pub fn get_food_level(&self) -> u8 {
self.hunger_manager.level.load()
}
pub async fn set_food_level(&self, food_level: u8) {
self.hunger_manager.set_level(food_level);
self.send_health().await;
}
pub fn get_saturation(&self) -> f32 {
self.hunger_manager.saturation.load()
}
pub async fn set_saturation(&self, saturation: f32) {
self.hunger_manager.set_saturation(saturation);
self.send_health().await;
}
pub async fn set_allow_flight(&self, allow: bool) {
self.abilities.lock().await.allow_flying = allow;
self.send_abilities_update().await;
}
pub async fn set_flying(&self, flying: bool) {
self.abilities.lock().await.flying = flying;
self.send_abilities_update().await;
}
pub async fn set_fly_speed(&self, speed: f32) {
self.abilities.lock().await.fly_speed = speed;
self.send_abilities_update().await;
}
pub async fn set_walk_speed(&self, speed: f32) {
self.abilities.lock().await.walk_speed = speed;
self.send_abilities_update().await;
}
pub async fn set_invulnerable(&self, invulnerable: bool) {
self.abilities.lock().await.invulnerable = invulnerable;
self.send_abilities_update().await;
}
pub fn get_exhaustion(&self) -> f32 {
self.hunger_manager.get_exhaustion()
}
@ -3258,6 +3467,15 @@ impl Player {
}
pub async fn swap_item(&self) {
if let Some(server) = self.world().server.upgrade()
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
{
let mut event = crate::plugin::api::events::player::player_swap_hands::PlayerSwapHandItemsEvent::new(player_arc);
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return;
}
}
let (main_hand_item, off_hand_item) = self.inventory.swap_item().await;
let equipment = &[
(EquipmentSlot::MAIN_HAND, main_hand_item),
@ -3386,6 +3604,18 @@ impl Player {
/// Sets the player's experience level and notifies the client.
pub async fn set_experience(&self, level: i32, progress: f32, points: i32) {
let old_level = self.experience_level.load(Ordering::Relaxed);
if old_level != level
&& let Some(server) = self.world().server.upgrade()
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
{
let mut event = crate::plugin::api::events::player::player_level_change::PlayerLevelChangeEvent::new(
player_arc,
old_level,
level,
);
server.plugin_manager.fire(&server, &mut event).await;
}
// TODO: These should be atomic together, not isolated; make a struct containing these. can cause ABA issues
self.experience_level.store(level, Ordering::Relaxed);
self.experience_progress.store(progress.clamp(0.0, 1.0));
@ -3429,6 +3659,19 @@ impl Player {
self.living_entity.add_effect(effect).await;
}
pub async fn has_effect(&self, effect_type: &'static StatusEffect) -> bool {
self.living_entity.has_effect(effect_type).await
}
pub async fn get_effect(&self, effect_type: &'static StatusEffect) -> Option<Effect> {
self.living_entity.get_effect(effect_type).await
}
pub async fn get_active_effects(&self) -> Vec<Effect> {
let effects = self.living_entity.active_effects.lock().await;
effects.values().cloned().collect()
}
pub async fn send_active_effects(&self) {
let effects = self.living_entity.active_effects.lock().await;
for effect in effects.values() {

View File

@ -1356,6 +1356,18 @@ impl JavaClient {
return;
};
let mut anim_event = crate::plugin::api::events::player::player_animation::PlayerAnimationEvent::new(
player.clone(),
match hand {
Hand::Left => crate::plugin::api::events::player::player_animation::PlayerAnimationType::ArmSwingOff,
Hand::Right => crate::plugin::api::events::player::player_animation::PlayerAnimationType::ArmSwingMain,
},
);
server.plugin_manager.fire(server, &mut anim_event).await;
if anim_event.cancelled {
return;
}
let (yaw, pitch) = player.rotation();
let hit_result = player
.world()

View File

@ -0,0 +1,23 @@
use pumpkin_macros::{Event, cancellable};
/// An event that occurs when an entity's remaining air supply changes.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityAirChangeEvent {
/// The ID of the entity.
pub entity_id: i32,
/// The new air amount.
pub amount: i32,
}
impl EntityAirChangeEvent {
#[must_use]
pub const fn new(entity_id: i32, amount: i32) -> Self {
Self {
entity_id,
amount,
cancelled: false,
}
}
}

View File

@ -0,0 +1,27 @@
use pumpkin_macros::{Event, cancellable};
/// An event that occurs when two entities breed to create a child.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityBreedEvent {
/// The ID of the parent 1 entity.
pub father_id: i32,
/// The ID of the parent 2 entity.
pub mother_id: i32,
/// The ID of the child entity.
pub child_id: i32,
}
impl EntityBreedEvent {
#[must_use]
pub const fn new(father_id: i32, mother_id: i32, child_id: i32) -> Self {
Self {
father_id,
mother_id,
child_id,
cancelled: false,
}
}
}

View File

@ -0,0 +1,50 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// The 16 colors available for dyeing entities.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DyeColor {
White,
Orange,
Magenta,
LightBlue,
Yellow,
Lime,
Pink,
Gray,
LightGray,
Cyan,
Purple,
Blue,
Brown,
Green,
Red,
Black,
}
/// An event that occurs when an entity is dyed.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityDyeEvent {
/// The ID of the dyed entity.
pub entity_id: i32,
/// The color applied.
pub color: DyeColor,
/// The player dyeing the entity, if any.
pub player: Option<Arc<Player>>,
}
impl EntityDyeEvent {
#[must_use]
pub const fn new(entity_id: i32, color: DyeColor, player: Option<Arc<Player>>) -> Self {
Self {
entity_id,
color,
player,
cancelled: false,
}
}
}

View File

@ -0,0 +1,28 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::vector3::Vector3;
/// An event that occurs when an entity explodes.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityExplodeEvent {
/// The ID of the exploding entity.
pub entity_id: i32,
/// The position where the explosion occurred.
pub position: Vector3<f64>,
/// The yield rate of blocks destroyed.
pub yield_rate: f32,
}
impl EntityExplodeEvent {
#[must_use]
pub const fn new(entity_id: i32, position: Vector3<f64>, yield_rate: f32) -> Self {
Self {
entity_id,
position,
yield_rate,
cancelled: false,
}
}
}

View File

@ -0,0 +1,23 @@
use pumpkin_macros::{Event, cancellable};
/// An event that occurs when an entity mounts another entity.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityMountEvent {
/// The ID of the mounting entity.
pub entity_id: i32,
/// The ID of the vehicle entity being mounted.
pub mount_id: i32,
}
impl EntityMountEvent {
#[must_use]
pub const fn new(entity_id: i32, mount_id: i32) -> Self {
Self {
entity_id,
mount_id,
cancelled: false,
}
}
}

View File

@ -0,0 +1,25 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// An event that occurs when an entity is tamed by a player.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityTameEvent {
/// The ID of the tamed entity.
pub entity_id: i32,
/// The player taming the entity.
pub owner: Arc<Player>,
}
impl EntityTameEvent {
#[must_use]
pub const fn new(entity_id: i32, owner: Arc<Player>) -> Self {
Self {
entity_id,
owner,
cancelled: false,
}
}
}

View File

@ -0,0 +1,23 @@
use pumpkin_macros::{Event, cancellable};
/// An event that occurs when an entity targets another entity.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityTargetEvent {
/// The ID of the targeting entity.
pub entity_id: i32,
/// The ID of the target entity, if any.
pub target_id: Option<i32>,
}
impl EntityTargetEvent {
#[must_use]
pub const fn new(entity_id: i32, target_id: Option<i32>) -> Self {
Self {
entity_id,
target_id,
cancelled: false,
}
}
}

View File

@ -0,0 +1,32 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::vector3::Vector3;
/// An event that occurs when an entity teleports.
#[cancellable]
#[derive(Event, Clone)]
pub struct EntityTeleportEvent {
/// The entity ID.
pub entity_id: i32,
/// The origin position.
pub from_position: Vector3<f64>,
/// The destination position.
pub to_position: Vector3<f64>,
}
impl EntityTeleportEvent {
#[must_use]
pub const fn new(
entity_id: i32,
from_position: Vector3<f64>,
to_position: Vector3<f64>,
) -> Self {
Self {
entity_id,
from_position,
to_position,
cancelled: false,
}
}
}

View File

@ -1,11 +1,27 @@
pub mod entity_air_change;
pub mod entity_breed;
pub mod entity_combust;
pub mod entity_damage;
pub mod entity_death;
pub mod entity_dye;
pub mod entity_explode;
pub mod entity_mount;
pub mod entity_regain_health;
pub mod entity_spawn;
pub mod entity_tame;
pub mod entity_target;
pub mod entity_teleport;
pub use entity_air_change::*;
pub use entity_breed::*;
pub use entity_combust::*;
pub use entity_damage::*;
pub use entity_death::*;
pub use entity_dye::*;
pub use entity_explode::*;
pub use entity_mount::*;
pub use entity_regain_health::*;
pub use entity_spawn::*;
pub use entity_tame::*;
pub use entity_target::*;
pub use entity_teleport::*;

View File

@ -0,0 +1,24 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::position::BlockPos;
/// An event that occurs when a brewing stand finishes brewing potion(s).
#[cancellable]
#[derive(Event, Clone)]
pub struct BrewEvent {
/// The block position of the brewing stand.
pub block_pos: BlockPos,
/// The remaining fuel power.
pub fuel_level: u8,
}
impl BrewEvent {
#[must_use]
pub const fn new(block_pos: BlockPos, fuel_level: u8) -> Self {
Self {
block_pos,
fuel_level,
cancelled: false,
}
}
}

View File

@ -0,0 +1,28 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::position::BlockPos;
/// An event that occurs when an item is consumed as fuel in a furnace.
#[cancellable]
#[derive(Event, Clone)]
pub struct FurnaceBurnEvent {
/// The position of the furnace block.
pub block_pos: BlockPos,
/// The registry key of the fuel item.
pub fuel_item: String,
/// The burn time in ticks.
pub burn_time: u32,
}
impl FurnaceBurnEvent {
#[must_use]
pub const fn new(block_pos: BlockPos, fuel_item: String, burn_time: u32) -> Self {
Self {
block_pos,
fuel_item,
burn_time,
cancelled: false,
}
}
}

View File

@ -1,9 +1,17 @@
pub mod brew;
pub mod craft_item;
pub mod furnace_burn;
pub mod furnace_smelt;
pub mod inventory_drag;
pub mod inventory_open;
pub mod prepare_anvil;
pub mod trade_select;
pub use brew::*;
pub use craft_item::*;
pub use furnace_burn::*;
pub use furnace_smelt::*;
pub use inventory_drag::*;
pub use inventory_open::*;
pub use prepare_anvil::*;
pub use trade_select::*;

View File

@ -0,0 +1,27 @@
use crate::entity::player::Player;
use pumpkin_macros::Event;
use std::sync::Arc;
/// An event that occurs when an item is prepared in an anvil.
#[derive(Event, Clone)]
pub struct PrepareAnvilEvent {
/// The player using the anvil.
pub player: Arc<Player>,
/// The name prepared in the anvil input text field.
pub rename_text: String,
/// The repair cost in experience levels.
pub repair_cost: u32,
}
impl PrepareAnvilEvent {
#[must_use]
pub const fn new(player: Arc<Player>, rename_text: String, repair_cost: u32) -> Self {
Self {
player,
rename_text,
repair_cost,
}
}
}

View File

@ -0,0 +1,25 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// An event that occurs when a player selects a trade recipe with a merchant.
#[cancellable]
#[derive(Event, Clone)]
pub struct TradeSelectEvent {
/// The player selecting the trade.
pub player: Arc<Player>,
/// The index of the selected trade slot.
pub slot_index: u8,
}
impl TradeSelectEvent {
#[must_use]
pub const fn new(player: Arc<Player>, slot_index: u8) -> Self {
Self {
player,
slot_index,
cancelled: false,
}
}
}

View File

@ -7,6 +7,7 @@ pub mod fish;
pub mod inventory_close;
pub mod inventory_interact;
pub mod item_held;
pub mod player_animation;
pub mod player_bed;
pub mod player_bucket;
pub mod player_change_world;
@ -18,14 +19,18 @@ pub mod player_gamemode_change;
pub mod player_interact_entity_event;
pub mod player_interact_event;
pub mod player_interact_unknown_entity_event;
pub mod player_item_break;
pub mod player_item_consume;
pub mod player_item_damage;
pub mod player_join;
pub mod player_kick;
pub mod player_leave;
pub mod player_level_change;
pub mod player_login;
pub mod player_move;
pub mod player_permission_check;
pub mod player_respawn;
pub mod player_swap_hands;
pub mod player_teleport;
pub mod player_toggle_flight_event;
pub mod player_toggle_sneak_event;
@ -40,6 +45,7 @@ pub use fish::*;
pub use inventory_close::*;
pub use inventory_interact::*;
pub use item_held::*;
pub use player_animation::*;
pub use player_bed::*;
pub use player_bucket::*;
pub use player_change_world::*;
@ -51,14 +57,18 @@ pub use player_gamemode_change::*;
pub use player_interact_entity_event::*;
pub use player_interact_event::*;
pub use player_interact_unknown_entity_event::*;
pub use player_item_break::*;
pub use player_item_consume::*;
pub use player_item_damage::*;
pub use player_join::*;
pub use player_kick::*;
pub use player_leave::*;
pub use player_level_change::*;
pub use player_login::*;
pub use player_move::*;
pub use player_permission_check::*;
pub use player_respawn::*;
pub use player_swap_hands::*;
pub use player_teleport::*;
pub use player_toggle_flight_event::*;
pub use player_toggle_sneak_event::*;

View File

@ -0,0 +1,33 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// The type of animation performed by a player.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlayerAnimationType {
ArmSwingMain,
ArmSwingOff,
LeaveBed,
}
/// An event that occurs when a player performs an animation.
#[cancellable]
#[derive(Event, Clone)]
pub struct PlayerAnimationEvent {
/// The player performing the animation.
pub player: Arc<Player>,
/// The type of animation.
pub animation_type: PlayerAnimationType,
}
impl PlayerAnimationEvent {
#[must_use]
pub const fn new(player: Arc<Player>, animation_type: PlayerAnimationType) -> Self {
Self {
player,
animation_type,
cancelled: false,
}
}
}

View File

@ -0,0 +1,20 @@
use crate::entity::player::Player;
use pumpkin_macros::Event;
use std::sync::Arc;
/// An event that occurs when a player breaks an item.
#[derive(Event, Clone)]
pub struct PlayerItemBreakEvent {
/// The player whose item broke.
pub player: Arc<Player>,
/// The registry key of the broken item.
pub item_name: String,
}
impl PlayerItemBreakEvent {
#[must_use]
pub const fn new(player: Arc<Player>, item_name: String) -> Self {
Self { player, item_name }
}
}

View File

@ -0,0 +1,25 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// An event that occurs when a player is kicked from the server.
#[cancellable]
#[derive(Event, Clone)]
pub struct PlayerKickEvent {
/// The player being kicked.
pub player: Arc<Player>,
/// The reason for the kick.
pub reason: String,
}
impl PlayerKickEvent {
#[must_use]
pub const fn new(player: Arc<Player>, reason: String) -> Self {
Self {
player,
reason,
cancelled: false,
}
}
}

View File

@ -0,0 +1,27 @@
use crate::entity::player::Player;
use pumpkin_macros::Event;
use std::sync::Arc;
/// An event that occurs when a player's experience level changes.
#[derive(Event, Clone)]
pub struct PlayerLevelChangeEvent {
/// The player whose level changed.
pub player: Arc<Player>,
/// The old level.
pub old_level: i32,
/// The new level.
pub new_level: i32,
}
impl PlayerLevelChangeEvent {
#[must_use]
pub const fn new(player: Arc<Player>, old_level: i32, new_level: i32) -> Self {
Self {
player,
old_level,
new_level,
}
}
}

View File

@ -0,0 +1,21 @@
use crate::entity::player::Player;
use pumpkin_macros::{Event, cancellable};
use std::sync::Arc;
/// An event that occurs when a player swaps items between main hand and offhand.
#[cancellable]
#[derive(Event, Clone)]
pub struct PlayerSwapHandItemsEvent {
/// The player swapping hand items.
pub player: Arc<Player>,
}
impl PlayerSwapHandItemsEvent {
#[must_use]
pub const fn new(player: Arc<Player>) -> Self {
Self {
player,
cancelled: false,
}
}
}

View File

@ -0,0 +1,24 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::vector3::Vector3;
/// An event that occurs when a generic game event is triggered in a world.
#[cancellable]
#[derive(Event, Clone)]
pub struct GenericGameEvent {
/// The key or type of the game event.
pub event_key: String,
/// The position where the game event occurred.
pub position: Vector3<f64>,
}
impl GenericGameEvent {
#[must_use]
pub const fn new(event_key: String, position: Vector3<f64>) -> Self {
Self {
event_key,
position,
cancelled: false,
}
}
}

View File

@ -0,0 +1,24 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::vector3::Vector3;
/// An event that occurs when a lightning bolt strikes in a world.
#[cancellable]
#[derive(Event, Clone)]
pub struct LightningStrikeEvent {
/// The location of the strike.
pub position: Vector3<f64>,
/// Whether this strike was spawned by an effect.
pub is_effect: bool,
}
impl LightningStrikeEvent {
#[must_use]
pub const fn new(position: Vector3<f64>, is_effect: bool) -> Self {
Self {
position,
is_effect,
cancelled: false,
}
}
}

View File

@ -1,13 +1,23 @@
pub mod chunk_load;
pub mod chunk_save;
pub mod chunk_send;
pub mod generic_game;
pub mod lightning_strike;
pub mod portal_create;
pub mod spawn_change;
pub mod structure_grow;
pub mod weather_change;
pub mod world_init;
pub mod world_load;
pub use chunk_load::*;
pub use chunk_save::*;
pub use chunk_send::*;
pub use generic_game::*;
pub use lightning_strike::*;
pub use portal_create::*;
pub use spawn_change::*;
pub use structure_grow::*;
pub use weather_change::*;
pub use world_init::*;
pub use world_load::*;

View File

@ -0,0 +1,32 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::position::BlockPos;
/// The type of portal created.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortalType {
Nether,
End,
Custom,
}
/// An event that occurs when a nether or end portal frame/block is created.
#[cancellable]
#[derive(Event, Clone)]
pub struct PortalCreateEvent {
/// The block position of the portal creation.
pub block_pos: BlockPos,
/// The type of portal created.
pub portal_type: PortalType,
}
impl PortalCreateEvent {
#[must_use]
pub const fn new(block_pos: BlockPos, portal_type: PortalType) -> Self {
Self {
block_pos,
portal_type,
cancelled: false,
}
}
}

View File

@ -0,0 +1,45 @@
use pumpkin_macros::{Event, cancellable};
use pumpkin_util::math::position::BlockPos;
/// The type of structure growing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TreeType {
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
Mangrove,
Cherry,
Azalea,
BrownMushroom,
RedMushroom,
Custom,
}
/// An event that occurs when a structure (like a tree or mushroom) grows.
#[cancellable]
#[derive(Event, Clone)]
pub struct StructureGrowEvent {
/// The origin block position where growth started.
pub block_pos: BlockPos,
/// The type of structure growing.
pub species: TreeType,
/// Whether bone meal was used.
pub bone_meal: bool,
}
impl StructureGrowEvent {
#[must_use]
pub const fn new(block_pos: BlockPos, species: TreeType, bone_meal: bool) -> Self {
Self {
block_pos,
species,
bone_meal,
cancelled: false,
}
}
}

View File

@ -0,0 +1,17 @@
use crate::world::World;
use pumpkin_macros::Event;
use std::sync::Arc;
/// An event that occurs when a world is initialized.
#[derive(Event, Clone)]
pub struct WorldInitEvent {
/// The world being initialized.
pub world: Arc<World>,
}
impl WorldInitEvent {
#[must_use]
pub const fn new(world: Arc<World>) -> Self {
Self { world }
}
}

View File

@ -1,11 +1,15 @@
pub mod context;
pub mod events;
pub mod gui;
pub mod tab_list;
pub mod title;
use std::{pin::Pin, sync::Arc};
pub use context::*;
pub use events::*;
pub use tab_list::*;
pub use title::*;
/// Struct representing metadata for a plugin.
///

View File

@ -0,0 +1,85 @@
use pumpkin_util::text::TextComponent;
/// Represents a Java Edition tab list configuration containing header and footer components.
#[derive(Debug, Clone)]
pub struct TabList {
/// The tab list header component.
pub header: TextComponent,
/// The tab list footer component.
pub footer: TextComponent,
}
impl Default for TabList {
fn default() -> Self {
Self {
header: TextComponent::text(""),
footer: TextComponent::text(""),
}
}
}
impl TabList {
/// Creates a new `TabListBuilder`.
#[must_use]
pub fn builder() -> TabListBuilder {
TabListBuilder::default()
}
}
/// A fluent builder for `TabList`.
#[derive(Debug, Clone, Default)]
pub struct TabListBuilder {
header: Option<TextComponent>,
footer: Option<TextComponent>,
}
impl TabListBuilder {
/// Creates a new `TabListBuilder`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Sets the tab list header component.
#[must_use]
pub fn header(mut self, header: impl Into<TextComponent>) -> Self {
self.header = Some(header.into());
self
}
/// Sets the tab list header from plain text.
#[must_use]
pub fn header_text(mut self, text: impl Into<String>) -> Self {
self.header = Some(TextComponent::text(text.into()));
self
}
/// Sets the tab list footer component.
#[must_use]
pub fn footer(mut self, footer: impl Into<TextComponent>) -> Self {
self.footer = Some(footer.into());
self
}
/// Sets the tab list footer from plain text.
#[must_use]
pub fn footer_text(mut self, text: impl Into<String>) -> Self {
self.footer = Some(TextComponent::text(text.into()));
self
}
/// Builds the `TabList`.
#[must_use]
pub fn build(self) -> TabList {
TabList {
header: self.header.unwrap_or_else(|| TextComponent::text("")),
footer: self.footer.unwrap_or_else(|| TextComponent::text("")),
}
}
}
impl From<TabListBuilder> for TabList {
fn from(builder: TabListBuilder) -> Self {
builder.build()
}
}

View File

@ -0,0 +1,93 @@
use crate::entity::player::{Player, TitleMode};
use pumpkin_util::text::TextComponent;
/// A fluent Builder for constructing and displaying titles, subtitles, and action bar text with animation timings.
#[derive(Debug, Clone, Default)]
pub struct TitleBuilder {
title: Option<TextComponent>,
subtitle: Option<TextComponent>,
actionbar: Option<TextComponent>,
fade_in: Option<i32>,
stay: Option<i32>,
fade_out: Option<i32>,
}
impl TitleBuilder {
/// Creates a new `TitleBuilder`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Sets the main title text component.
#[must_use]
pub fn title(mut self, title: impl Into<TextComponent>) -> Self {
self.title = Some(title.into());
self
}
/// Sets the main title from plain text.
#[must_use]
pub fn title_text(mut self, text: impl Into<String>) -> Self {
self.title = Some(TextComponent::text(text.into()));
self
}
/// Sets the subtitle text component.
#[must_use]
pub fn subtitle(mut self, subtitle: impl Into<TextComponent>) -> Self {
self.subtitle = Some(subtitle.into());
self
}
/// Sets the subtitle from plain text.
#[must_use]
pub fn subtitle_text(mut self, text: impl Into<String>) -> Self {
self.subtitle = Some(TextComponent::text(text.into()));
self
}
/// Sets the action bar text component.
#[must_use]
pub fn actionbar(mut self, actionbar: impl Into<TextComponent>) -> Self {
self.actionbar = Some(actionbar.into());
self
}
/// Sets the action bar text from plain text.
#[must_use]
pub fn actionbar_text(mut self, text: impl Into<String>) -> Self {
self.actionbar = Some(TextComponent::text(text.into()));
self
}
/// Sets the fade-in, stay, and fade-out timings in ticks.
#[must_use]
pub const fn times(mut self, fade_in: i32, stay: i32, fade_out: i32) -> Self {
self.fade_in = Some(fade_in);
self.stay = Some(stay);
self.fade_out = Some(fade_out);
self
}
/// Sends the title configuration to the specified player.
pub async fn send_to(&self, player: &Player) {
if let (Some(fade_in), Some(stay), Some(fade_out)) =
(self.fade_in, self.stay, self.fade_out)
{
player.send_title_animation(fade_in, stay, fade_out).await;
}
if let Some(ref title) = self.title {
player.show_title(title, &TitleMode::Title).await;
}
if let Some(ref subtitle) = self.subtitle {
player.show_title(subtitle, &TitleMode::SubTitle).await;
}
if let Some(ref actionbar) = self.actionbar {
player.show_title(actionbar, &TitleMode::ActionBar).await;
}
}
}

View File

@ -3,6 +3,9 @@ use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3;
use crate::entity::ai::goal::{Goal, GoalFuture};
use crate::entity::mob::Mob;
use crate::plugin::loader::wasm::wasm_host::{PluginInstance, WasmPlugin};
use crate::plugin::loader::wasm::wasm_host::{
state::{EntityResource, PluginHostState},
wit::v0_1::events::to_wasm_position,
@ -501,6 +504,32 @@ impl HostEntity for PluginHostState {
Ok(())
}
async fn is_silent(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().is_silent())
}
async fn set_silent(&mut self, entity: Resource<Entity>, silent: bool) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().set_silent(silent);
Ok(())
}
async fn has_gravity(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(!entity.get_entity().has_no_gravity())
}
async fn set_has_gravity(
&mut self,
entity: Resource<Entity>,
gravity: bool,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().set_has_no_gravity(!gravity);
Ok(())
}
async fn get_eye_height(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_dimension.load().eye_height)
@ -804,6 +833,118 @@ impl HostEntity for PluginHostState {
Ok(())
}
async fn add_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
match goal {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::Swim => {
mob_entity.add_goal(priority, crate::entity::ai::goal::swim::SwimGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::WanderAround(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::wander_around::WanderAroundGoal::new(f64::from(speed)));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::MeleeAttack(speed) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::melee_attack::MeleeAttackGoal::new(f64::from(speed), false));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAtPlayer(range) => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_at_entity::LookAtEntityGoal::new(
std::sync::Weak::<crate::entity::mob::zombie::zombie::ZombieEntity>::new() as std::sync::Weak<dyn crate::entity::mob::Mob>,
&pumpkin_data::entity::EntityType::PLAYER,
range,
0.02,
false,
));
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::LookAround => {
mob_entity.add_goal(priority, crate::entity::ai::goal::look_around::RandomLookAroundGoal::default());
}
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::BuiltinAiGoal::EscapeDanger(speed) => {
mob_entity.add_goal(priority, *crate::entity::ai::goal::escape_danger::EscapeDangerGoal::new(f64::from(speed)));
}
_ => {} // Remaining goals
}
}
Ok(())
}
async fn add_custom_ai_goal(
&mut self,
entity: Resource<Entity>,
priority: u8,
goal_id: u32,
) -> wasmtime::Result<()> {
let plugin = self.plugin.as_ref().unwrap().upgrade().unwrap();
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
let mob_entity = mob.get_mob_entity();
mob_entity.add_goal(priority, CustomWasmGoal { plugin, goal_id });
}
Ok(())
}
async fn clear_ai_goals(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().clear_ai_goals(mob).await;
}
Ok(())
}
async fn set_ai_disabled(
&mut self,
entity: Resource<Entity>,
disabled: bool,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_no_ai(disabled);
}
Ok(())
}
async fn is_ai_disabled(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_mob()
.is_none_or(|mob| mob.get_mob_entity().is_no_ai()))
}
async fn set_target(
&mut self,
entity: Resource<Entity>,
target: Option<Resource<Entity>>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let target_entity = if let Some(t) = target {
Some(entity_from_resource(self, &t)?)
} else {
None
};
if let Some(mob) = entity.get_mob() {
mob.get_mob_entity().set_target(target_entity).await;
}
Ok(())
}
async fn get_target(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
if let Some(mob) = entity.get_mob()
&& let Some(target) = mob.get_mob_entity().get_target().await
{
return Ok(Some(self.add_entity(target)?));
}
Ok(None)
}
async fn raycast(
&mut self,
entity: Resource<Entity>,
@ -848,3 +989,156 @@ impl HostEntity for PluginHostState {
Ok(())
}
}
pub struct CustomWasmGoal {
pub plugin: Arc<WasmPlugin>,
pub goal_id: u32,
}
impl Goal for CustomWasmGoal {
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
Box::pin(async {
let mut store = self.plugin.store.lock().await;
if let Some(entity_arc) = mob
.get_entity()
.world
.load()
.get_entity_by_id(mob.get_entity().entity_id)
{
match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => {
let server = store.data_mut().server.clone().unwrap();
let server_res = store.data_mut().add_server(server).unwrap();
let entity_res = store.data_mut().add_entity(entity_arc).unwrap();
plugin
.call_handle_ai_goal_can_start(
&mut *store,
self.goal_id,
server_res,
entity_res,
)
.await
.unwrap_or(false)
}
}
} else {
false
}
})
}
fn should_continue<'a>(&'a self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
Box::pin(async {
let mut store = self.plugin.store.lock().await;
if let Some(entity_arc) = mob
.get_entity()
.world
.load()
.get_entity_by_id(mob.get_entity().entity_id)
{
match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => {
let server = store.data_mut().server.clone().unwrap();
let server_res = store.data_mut().add_server(server).unwrap();
let entity_res = store.data_mut().add_entity(entity_arc).unwrap();
plugin
.call_handle_ai_goal_should_continue(
&mut *store,
self.goal_id,
server_res,
entity_res,
)
.await
.unwrap_or(false)
}
}
} else {
false
}
})
}
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async {
let mut store = self.plugin.store.lock().await;
if let Some(entity_arc) = mob
.get_entity()
.world
.load()
.get_entity_by_id(mob.get_entity().entity_id)
{
match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => {
let server = store.data_mut().server.clone().unwrap();
let server_res = store.data_mut().add_server(server).unwrap();
let entity_res = store.data_mut().add_entity(entity_arc).unwrap();
let _ = plugin
.call_handle_ai_goal_start(
&mut *store,
self.goal_id,
server_res,
entity_res,
)
.await;
}
}
}
})
}
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async {
let mut store = self.plugin.store.lock().await;
if let Some(entity_arc) = mob
.get_entity()
.world
.load()
.get_entity_by_id(mob.get_entity().entity_id)
{
match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => {
let server = store.data_mut().server.clone().unwrap();
let server_res = store.data_mut().add_server(server).unwrap();
let entity_res = store.data_mut().add_entity(entity_arc).unwrap();
let _ = plugin
.call_handle_ai_goal_tick(
&mut *store,
self.goal_id,
server_res,
entity_res,
)
.await;
}
}
}
})
}
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
Box::pin(async {
let mut store = self.plugin.store.lock().await;
if let Some(entity_arc) = mob
.get_entity()
.world
.load()
.get_entity_by_id(mob.get_entity().entity_id)
{
match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => {
let server = store.data_mut().server.clone().unwrap();
let server_res = store.data_mut().add_server(server).unwrap();
let entity_res = store.data_mut().add_entity(entity_arc).unwrap();
let _ = plugin
.call_handle_ai_goal_stop(
&mut *store,
self.goal_id,
server_res,
entity_res,
)
.await;
}
}
}
})
}
}

View File

@ -206,9 +206,9 @@ pub fn serialize_java_packet(
Some(buf.into())
}
ClientboundPacket::CDebugSample(data) => {
let sample_vec: Vec<i64> = data.sample.iter().map(|v| *v as i64).collect();
let vec_sample: Vec<_> = data.sample.iter().map(|v| *v as _).collect();
let p = pumpkin_protocol::java::client::play::CDebugSample {
sample: &sample_vec,
sample: &vec_sample,
sample_type: VarInt(data.sample_type),
};
let mut buf = Vec::new();

View File

@ -28,6 +28,7 @@ pub mod recipe;
pub mod scheduler;
pub mod scoreboard;
pub mod server;
pub mod status_effect;
pub mod text;
pub mod uuid;
pub mod world;

View File

@ -868,6 +868,395 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(())
}
async fn add_effect(
&mut self,
player: Resource<Player>,
effect: pumpkin::plugin::status_effect::StatusEffectInstance,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let effect_type = super::status_effect::from_wasm_status_effect_type(effect.effect_type);
if let Some(status_effect) =
pumpkin_data::effect::StatusEffect::from_name(effect_type.to_name())
{
let effect_obj = pumpkin_data::potion::Effect {
effect_type: status_effect,
duration: effect.duration as i32,
amplifier: effect.amplifier,
ambient: effect.ambient,
show_particles: effect.show_particles,
show_icon: effect.show_icon,
blend: false,
};
player.add_effect(effect_obj).await;
}
Ok(())
}
async fn remove_effect(
&mut self,
player: Resource<Player>,
effect: pumpkin::plugin::status_effect::StatusEffectType,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let effect_type = super::status_effect::from_wasm_status_effect_type(effect);
if let Some(status_effect) =
pumpkin_data::effect::StatusEffect::from_name(effect_type.to_name())
{
player.remove_effect(status_effect).await;
}
Ok(())
}
async fn clear_effects(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.remove_all_effects().await;
Ok(())
}
async fn has_effect(
&mut self,
player: Resource<Player>,
effect: pumpkin::plugin::status_effect::StatusEffectType,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
let effect_type = super::status_effect::from_wasm_status_effect_type(effect);
if let Some(status_effect) =
pumpkin_data::effect::StatusEffect::from_name(effect_type.to_name())
{
Ok(player.has_effect(status_effect).await)
} else {
Ok(false)
}
}
async fn get_effect(
&mut self,
player: Resource<Player>,
effect: pumpkin::plugin::status_effect::StatusEffectType,
) -> wasmtime::Result<Option<pumpkin::plugin::status_effect::StatusEffectInstance>> {
let player = player_from_resource(self, &player)?;
let effect_type = super::status_effect::from_wasm_status_effect_type(effect);
if let Some(status_effect) =
pumpkin_data::effect::StatusEffect::from_name(effect_type.to_name())
&& let Some(eff) = player.get_effect(status_effect).await
{
return Ok(super::status_effect::to_wasm_status_effect_instance(&eff));
}
Ok(None)
}
async fn get_active_effects(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<Vec<pumpkin::plugin::status_effect::StatusEffectInstance>> {
let player = player_from_resource(self, &player)?;
let effects = player.get_active_effects().await;
let mut list = Vec::with_capacity(effects.len());
for eff in &effects {
if let Some(instance) = super::status_effect::to_wasm_status_effect_instance(eff) {
list.push(instance);
}
}
Ok(list)
}
async fn heal(&mut self, player: Resource<Player>, amount: f32) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.heal(amount).await;
Ok(())
}
async fn damage(&mut self, player: Resource<Player>, amount: f32) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.damage_generic(amount).await;
Ok(())
}
async fn kill(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.kill().await;
Ok(())
}
async fn start_cooldown(
&mut self,
player: Resource<Player>,
group: String,
duration_ticks: i32,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.start_cooldown(group, duration_ticks).await;
Ok(())
}
async fn get_cooldown(
&mut self,
player: Resource<Player>,
group: String,
) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.get_cooldown(&group).await)
}
async fn is_on_cooldown(
&mut self,
player: Resource<Player>,
group: String,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.is_on_cooldown(&group).await)
}
async fn set_allow_flight(
&mut self,
player: Resource<Player>,
allowed: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_allow_flight(allowed).await;
Ok(())
}
async fn set_fly_speed(
&mut self,
player: Resource<Player>,
speed: f32,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_fly_speed(speed).await;
Ok(())
}
async fn set_walk_speed(
&mut self,
player: Resource<Player>,
speed: f32,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_walk_speed(speed).await;
Ok(())
}
async fn set_invulnerable(
&mut self,
player: Resource<Player>,
invulnerable: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_invulnerable(invulnerable).await;
Ok(())
}
async fn set_player_time(
&mut self,
player: Resource<Player>,
time: u64,
relative: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.set_player_time(
&player.living_entity.entity.world.load_full(),
time,
relative,
)
.await;
Ok(())
}
async fn reset_player_time(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.reset_player_time(&player.living_entity.entity.world.load_full())
.await;
Ok(())
}
async fn get_player_time(&mut self, player: Resource<Player>) -> wasmtime::Result<Option<u64>> {
let player = player_from_resource(self, &player)?;
Ok(player.get_player_time())
}
async fn is_player_time_relative(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.is_player_time_relative())
}
async fn set_player_weather(
&mut self,
player: Resource<Player>,
weather: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let w = match weather {
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather::Clear => crate::entity::player::PlayerWeather::Clear,
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather::Downfall => crate::entity::player::PlayerWeather::Downfall,
};
player.set_player_weather(w);
Ok(())
}
async fn reset_player_weather(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.reset_player_weather();
Ok(())
}
async fn get_player_weather(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<Option<crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather>>{
let player = player_from_resource(self, &player)?;
Ok(player.get_player_weather().map(|w| match w {
crate::entity::player::PlayerWeather::Clear => crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather::Clear,
crate::entity::player::PlayerWeather::Downfall => crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::PlayerWeather::Downfall,
}))
}
async fn set_compass_target(
&mut self,
player: Resource<Player>,
pos: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let pos_vec = from_wasm_position(pos);
let block_pos = pumpkin_util::math::position::BlockPos::new(
pos_vec.x as i32,
pos_vec.y as i32,
pos_vec.z as i32,
);
player.set_compass_target(block_pos).await;
Ok(())
}
async fn get_compass_target(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position,
> {
let player = player_from_resource(self, &player)?;
let target = player
.get_compass_target()
.unwrap_or(pumpkin_util::math::position::BlockPos::new(0, 0, 0));
let vec3 = pumpkin_util::math::vector3::Vector3::new(
f64::from(target.0.x),
f64::from(target.0.y),
f64::from(target.0.z),
);
Ok(to_wasm_position(vec3))
}
async fn set_respawn_location(
&mut self,
player: Resource<Player>,
pos: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let pos_vec = from_wasm_position(pos);
let block_pos = pumpkin_util::math::position::BlockPos::new(
pos_vec.x as i32,
pos_vec.y as i32,
pos_vec.z as i32,
);
player.set_respawn_location(block_pos);
Ok(())
}
async fn get_respawn_location(
&mut self,
player: Resource<Player>,
) -> wasmtime::Result<
Option<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position,
>,
> {
let player = player_from_resource(self, &player)?;
Ok(player.get_respawn_location().map(|p| {
let vec3 = pumpkin_util::math::vector3::Vector3::new(
f64::from(p.0.x),
f64::from(p.0.y),
f64::from(p.0.z),
);
to_wasm_position(vec3)
}))
}
async fn hide_player(
&mut self,
player: Resource<Player>,
other: Resource<Player>,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let other = player_from_resource(self, &other)?;
player.hide_player(other.gameprofile.id).await;
Ok(())
}
async fn show_player(
&mut self,
player: Resource<Player>,
other: Resource<Player>,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let other = player_from_resource(self, &other)?;
player.show_player(other.gameprofile.id).await;
Ok(())
}
async fn can_see(
&mut self,
player: Resource<Player>,
other: Resource<Player>,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
let other = player_from_resource(self, &other)?;
Ok(player.can_see(&other.gameprofile.id).await)
}
async fn get_target_block(
&mut self,
player: Resource<Player>,
max_distance: u32,
) -> wasmtime::Result<
Option<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position,
>,
> {
let player = player_from_resource(self, &player)?;
let res = player
.get_target_block(
&player.living_entity.entity.world.load_full(),
f64::from(max_distance),
)
.await;
Ok(res.map(|p| {
let vec3 = pumpkin_util::math::vector3::Vector3::new(
f64::from(p.0.x),
f64::from(p.0.y),
f64::from(p.0.z),
);
to_wasm_position(vec3)
}))
}
async fn launch_projectile(
&mut self,
_player: Resource<Player>,
_type_: crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::ProjectileType,
) -> wasmtime::Result<
Option<
Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::Entity,
>,
>,
> {
Ok(None)
}
async fn set_tab_list_header_footer(
&mut self,
player: Resource<Player>,

View File

@ -0,0 +1,242 @@
use crate::plugin::loader::wasm::wasm_host::{
state::PluginHostState, wit::v0_1::pumpkin::plugin::status_effect,
};
impl status_effect::Host for PluginHostState {}
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn from_wasm_status_effect_type(
t: status_effect::StatusEffectType,
) -> pumpkin_data::status_effect::EffectType {
match t {
status_effect::StatusEffectType::Speed => pumpkin_data::status_effect::EffectType::Speed,
status_effect::StatusEffectType::Slowness => {
pumpkin_data::status_effect::EffectType::Slowness
}
status_effect::StatusEffectType::Haste => pumpkin_data::status_effect::EffectType::Haste,
status_effect::StatusEffectType::MiningFatigue => {
pumpkin_data::status_effect::EffectType::MiningFatigue
}
status_effect::StatusEffectType::Strength => {
pumpkin_data::status_effect::EffectType::Strength
}
status_effect::StatusEffectType::InstantHealth => {
pumpkin_data::status_effect::EffectType::InstantHealth
}
status_effect::StatusEffectType::InstantDamage => {
pumpkin_data::status_effect::EffectType::InstantDamage
}
status_effect::StatusEffectType::JumpBoost => {
pumpkin_data::status_effect::EffectType::JumpBoost
}
status_effect::StatusEffectType::Nausea => pumpkin_data::status_effect::EffectType::Nausea,
status_effect::StatusEffectType::Regeneration => {
pumpkin_data::status_effect::EffectType::Regeneration
}
status_effect::StatusEffectType::Resistance => {
pumpkin_data::status_effect::EffectType::Resistance
}
status_effect::StatusEffectType::FireResistance => {
pumpkin_data::status_effect::EffectType::FireResistance
}
status_effect::StatusEffectType::WaterBreathing => {
pumpkin_data::status_effect::EffectType::WaterBreathing
}
status_effect::StatusEffectType::Invisibility => {
pumpkin_data::status_effect::EffectType::Invisibility
}
status_effect::StatusEffectType::Blindness => {
pumpkin_data::status_effect::EffectType::Blindness
}
status_effect::StatusEffectType::NightVision => {
pumpkin_data::status_effect::EffectType::NightVision
}
status_effect::StatusEffectType::Hunger => pumpkin_data::status_effect::EffectType::Hunger,
status_effect::StatusEffectType::Weakness => {
pumpkin_data::status_effect::EffectType::Weakness
}
status_effect::StatusEffectType::Poison => pumpkin_data::status_effect::EffectType::Poison,
status_effect::StatusEffectType::Wither => pumpkin_data::status_effect::EffectType::Wither,
status_effect::StatusEffectType::HealthBoost => {
pumpkin_data::status_effect::EffectType::HealthBoost
}
status_effect::StatusEffectType::Absorption => {
pumpkin_data::status_effect::EffectType::Absorption
}
status_effect::StatusEffectType::Saturation => {
pumpkin_data::status_effect::EffectType::Saturation
}
status_effect::StatusEffectType::Glowing => {
pumpkin_data::status_effect::EffectType::Glowing
}
status_effect::StatusEffectType::Levitation => {
pumpkin_data::status_effect::EffectType::Levitation
}
status_effect::StatusEffectType::Luck => pumpkin_data::status_effect::EffectType::Luck,
status_effect::StatusEffectType::Unluck => pumpkin_data::status_effect::EffectType::Unluck,
status_effect::StatusEffectType::SlowFalling => {
pumpkin_data::status_effect::EffectType::SlowFalling
}
status_effect::StatusEffectType::ConduitPower => {
pumpkin_data::status_effect::EffectType::ConduitPower
}
status_effect::StatusEffectType::DolphinsGrace => {
pumpkin_data::status_effect::EffectType::DolphinsGrace
}
status_effect::StatusEffectType::BadOmen => {
pumpkin_data::status_effect::EffectType::BadOmen
}
status_effect::StatusEffectType::HeroOfTheVillage => {
pumpkin_data::status_effect::EffectType::HeroOfTheVillage
}
status_effect::StatusEffectType::Darkness => {
pumpkin_data::status_effect::EffectType::Darkness
}
status_effect::StatusEffectType::TrialOmen => {
pumpkin_data::status_effect::EffectType::TrialOmen
}
status_effect::StatusEffectType::RaidOmen => {
pumpkin_data::status_effect::EffectType::RaidOmen
}
status_effect::StatusEffectType::WindCharged => {
pumpkin_data::status_effect::EffectType::WindCharged
}
status_effect::StatusEffectType::Weaving => {
pumpkin_data::status_effect::EffectType::Weaving
}
status_effect::StatusEffectType::Oozing => pumpkin_data::status_effect::EffectType::Oozing,
status_effect::StatusEffectType::Infested => {
pumpkin_data::status_effect::EffectType::Infested
}
}
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn to_wasm_status_effect_type(
t: pumpkin_data::status_effect::EffectType,
) -> status_effect::StatusEffectType {
match t {
pumpkin_data::status_effect::EffectType::Speed => status_effect::StatusEffectType::Speed,
pumpkin_data::status_effect::EffectType::Slowness => {
status_effect::StatusEffectType::Slowness
}
pumpkin_data::status_effect::EffectType::Haste => status_effect::StatusEffectType::Haste,
pumpkin_data::status_effect::EffectType::MiningFatigue => {
status_effect::StatusEffectType::MiningFatigue
}
pumpkin_data::status_effect::EffectType::Strength => {
status_effect::StatusEffectType::Strength
}
pumpkin_data::status_effect::EffectType::InstantHealth => {
status_effect::StatusEffectType::InstantHealth
}
pumpkin_data::status_effect::EffectType::InstantDamage => {
status_effect::StatusEffectType::InstantDamage
}
pumpkin_data::status_effect::EffectType::JumpBoost => {
status_effect::StatusEffectType::JumpBoost
}
pumpkin_data::status_effect::EffectType::Nausea => status_effect::StatusEffectType::Nausea,
pumpkin_data::status_effect::EffectType::Regeneration => {
status_effect::StatusEffectType::Regeneration
}
pumpkin_data::status_effect::EffectType::Resistance => {
status_effect::StatusEffectType::Resistance
}
pumpkin_data::status_effect::EffectType::FireResistance => {
status_effect::StatusEffectType::FireResistance
}
pumpkin_data::status_effect::EffectType::WaterBreathing => {
status_effect::StatusEffectType::WaterBreathing
}
pumpkin_data::status_effect::EffectType::Invisibility => {
status_effect::StatusEffectType::Invisibility
}
pumpkin_data::status_effect::EffectType::Blindness => {
status_effect::StatusEffectType::Blindness
}
pumpkin_data::status_effect::EffectType::NightVision => {
status_effect::StatusEffectType::NightVision
}
pumpkin_data::status_effect::EffectType::Hunger => status_effect::StatusEffectType::Hunger,
pumpkin_data::status_effect::EffectType::Weakness => {
status_effect::StatusEffectType::Weakness
}
pumpkin_data::status_effect::EffectType::Poison => status_effect::StatusEffectType::Poison,
pumpkin_data::status_effect::EffectType::Wither => status_effect::StatusEffectType::Wither,
pumpkin_data::status_effect::EffectType::HealthBoost => {
status_effect::StatusEffectType::HealthBoost
}
pumpkin_data::status_effect::EffectType::Absorption => {
status_effect::StatusEffectType::Absorption
}
pumpkin_data::status_effect::EffectType::Saturation => {
status_effect::StatusEffectType::Saturation
}
pumpkin_data::status_effect::EffectType::Glowing => {
status_effect::StatusEffectType::Glowing
}
pumpkin_data::status_effect::EffectType::Levitation => {
status_effect::StatusEffectType::Levitation
}
pumpkin_data::status_effect::EffectType::Luck => status_effect::StatusEffectType::Luck,
pumpkin_data::status_effect::EffectType::Unluck => status_effect::StatusEffectType::Unluck,
pumpkin_data::status_effect::EffectType::SlowFalling => {
status_effect::StatusEffectType::SlowFalling
}
pumpkin_data::status_effect::EffectType::ConduitPower => {
status_effect::StatusEffectType::ConduitPower
}
pumpkin_data::status_effect::EffectType::DolphinsGrace => {
status_effect::StatusEffectType::DolphinsGrace
}
pumpkin_data::status_effect::EffectType::BadOmen => {
status_effect::StatusEffectType::BadOmen
}
pumpkin_data::status_effect::EffectType::HeroOfTheVillage => {
status_effect::StatusEffectType::HeroOfTheVillage
}
pumpkin_data::status_effect::EffectType::Darkness => {
status_effect::StatusEffectType::Darkness
}
pumpkin_data::status_effect::EffectType::TrialOmen => {
status_effect::StatusEffectType::TrialOmen
}
pumpkin_data::status_effect::EffectType::RaidOmen => {
status_effect::StatusEffectType::RaidOmen
}
pumpkin_data::status_effect::EffectType::WindCharged => {
status_effect::StatusEffectType::WindCharged
}
pumpkin_data::status_effect::EffectType::Weaving => {
status_effect::StatusEffectType::Weaving
}
pumpkin_data::status_effect::EffectType::Oozing => status_effect::StatusEffectType::Oozing,
pumpkin_data::status_effect::EffectType::Infested => {
status_effect::StatusEffectType::Infested
}
}
}
#[must_use]
pub fn to_wasm_status_effect_instance(
effect: &pumpkin_data::potion::Effect,
) -> Option<status_effect::StatusEffectInstance> {
let name = effect
.effect_type
.minecraft_name
.strip_prefix("minecraft:")
.unwrap_or(effect.effect_type.minecraft_name);
let effect_type_enum = pumpkin_data::status_effect::EffectType::from_name(name)?;
let wasm_type = to_wasm_status_effect_type(effect_type_enum);
Some(status_effect::StatusEffectInstance {
effect_type: wasm_type,
duration: u32::try_from(effect.duration).unwrap_or(0),
amplifier: effect.amplifier,
ambient: effect.ambient,
show_particles: effect.show_particles,
show_icon: effect.show_icon,
})
}

View File

@ -13,6 +13,7 @@ use crate::block::entities::command_block::CommandBlockEntity as InternalCommand
use crate::block::entities::jukebox::JukeboxBlockEntity as InternalJukeboxBlockEntity;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity as InternalMobSpawnerBlockEntity;
use crate::block::entities::sign::SignBlockEntity as InternalSignBlockEntity;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position as WitPosition;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{
BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags,
BlockPos as WitBlockPos, BlockState as WitBlockState, BoundingBox as WitBoundingBox,
@ -677,6 +678,43 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
Ok(entities)
}
async fn strike_lightning(
&mut self,
world: Resource<World>,
pos: WitPosition,
effect_only: bool,
) -> wasmtime::Result<()> {
let world_provider = self.get_world_res(&world)?.provider.clone();
let internal_pos = super::events::from_wasm_position(pos);
world_provider
.strike_lightning(internal_pos, effect_only)
.await;
Ok(())
}
async fn ray_trace_blocks(
&mut self,
world: Resource<World>,
start: WitPosition,
end: WitPosition,
) -> wasmtime::Result<Option<WitPosition>> {
let world_provider = self.get_world_res(&world)?.provider.clone();
let start_pos = super::events::from_wasm_position(start);
let end_pos = super::events::from_wasm_position(end);
let res = world_provider
.raycast(start_pos, end_pos, async |pos, w| {
!w.get_block_state(pos).is_air()
})
.await;
Ok(res.map(|(p, _)| {
super::events::to_wasm_position(pumpkin_util::math::vector3::Vector3::new(
f64::from(p.0.x),
f64::from(p.0.y),
f64::from(p.0.z),
))
}))
}
async fn get_block_entity(
&mut self,
world: Resource<World>,

View File

@ -448,6 +448,12 @@ impl Server {
new_worlds.push(world.clone());
new_worlds
});
let mut event =
crate::plugin::api::events::world::world_init::WorldInitEvent::new(world.clone());
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current()
.block_on(server.plugin_manager.fire(&server, &mut event));
});
world
})
.await

View File

@ -4729,6 +4729,18 @@ impl World {
self.spawn_entity(item_entity).await;
}
pub async fn strike_lightning(self: &Arc<Self>, pos: Vector3<f64>, _effect_only: bool) {
use pumpkin_data::entity::EntityType;
use uuid::Uuid;
let lightning = crate::entity::r#type::from_type(
&EntityType::LIGHTNING_BOLT,
pos,
self,
Uuid::new_v4(),
);
self.spawn_entity(lightning).await;
}
/* ItemScatterer.java */
pub async fn scatter_inventory(
self: &Arc<Self>,