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 { } else {
format!("vec_{}", tmp) 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 { } else {
format!("{}.iter().map(|v| *v as _).collect()", src) format!("{}.iter().map(|v| *v as _).collect()", src)
} }

View File

@ -294,6 +294,11 @@ pub mod fuels;
#[path = "generated/effect.rs"] #[path = "generated/effect.rs"]
pub mod effect; pub mod effect;
#[cfg(feature = "effect")]
#[rustfmt::skip]
#[path = "generated/status_effect.rs"]
pub mod status_effect;
#[cfg(feature = "structures")] #[cfg(feature = "structures")]
#[rustfmt::skip] #[rustfmt::skip]
#[path = "generated/structures.rs"] #[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(); let mut handlers = TASK_HANDLERS.lock().unwrap();
handlers.handle(handler_id, server); 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. /// 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 { fn uses_any(prioritized_goal: &PrioritizedGoal, controls: Controls) -> bool {
let goal_controls = prioritized_goal.controls(); let goal_controls = prioritized_goal.controls();
for control in Controls::ITER { for control in Controls::ITER {

View File

@ -71,6 +71,20 @@ impl BreathManager {
let new_air = (prev - AIR_DEPLETION_RATE).max(0); let new_air = (prev - AIR_DEPLETION_RATE).max(0);
if new_air != prev { if new_air != prev {
self.air_supply.store(new_air, Ordering::Relaxed); 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); self.send_air_supply(player);
} }

View File

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

View File

@ -216,6 +216,22 @@ impl EndermanEntity {
world.play_sound(Sound::EntityEndermanTeleport, SoundCategory::Hostile, pos); 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); entity.set_pos(new_pos);
let chunk_pos = entity.chunk_pos.load(); let chunk_pos = entity.chunk_pos.load();
world.broadcast_to_chunk( world.broadcast_to_chunk(

View File

@ -167,6 +167,45 @@ impl MobEntity {
(self.mob_flags.load(Relaxed) & Self::AI_DISABLED_FLAG) != 0 (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) { fn set_mob_flag(&self, flag: u8, value: bool) {
let old_b = self.mob_flags.load(Ordering::Relaxed); 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) {} fn mob_set_variant_name(&self, _name: &str) {}
} }
impl<T: Mob + Send + 'static> EntityBase for T { impl<T: Mob + Send + 'static> EntityBase for T {
fn get_mob(&self) -> Option<&dyn Mob> {
Some(self)
}
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> { fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
self.mob_init_data_tracker().await; self.mob_init_data_tracker().await;

View File

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

View File

@ -418,6 +418,12 @@ pub struct ItemCooldown {
pub duration: i32, pub duration: i32,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlayerWeather {
Clear,
Downfall,
}
pub struct Player { pub struct Player {
/// The underlying living entity object that represents the player. /// The underlying living entity object that represents the player.
pub living_entity: LivingEntity, pub living_entity: LivingEntity,
@ -520,6 +526,11 @@ pub struct Player {
pub tab_list_order: AtomicI32, pub tab_list_order: AtomicI32,
pub tab_list_latency: AtomicI32, pub tab_list_latency: AtomicI32,
pub tab_list_listed: AtomicBool, 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 advancements: Arc<Mutex<PlayerAdvancement>>,
pub enchantment_seed: AtomicI32, pub enchantment_seed: AtomicI32,
pub fishing_bobber: AtomicI32, pub fishing_bobber: AtomicI32,
@ -753,11 +764,26 @@ impl Player {
tab_list_order: AtomicI32::new(0), tab_list_order: AtomicI32::new(0),
tab_list_latency: AtomicI32::new(0), tab_list_latency: AtomicI32::new(0),
tab_list_listed: AtomicBool::new(true), 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), fishing_bobber: AtomicI32::new(-1),
bedrock_skin: ArcSwap::new(Arc::new(bedrock_skin)), 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) { pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) {
*self.tab_list_header.lock().await = header.clone(); *self.tab_list_header.lock().await = header.clone();
*self.tab_list_footer.lock().await = footer.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 // Send the break status before clearing the slot so the client can
// use the item texture for break particles. // use the item texture for break particles.
if result == pumpkin_data::item_stack::DamageResult::Broken { 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( self.increment_stat(
statistics::StatisticCategory::Broken, statistics::StatisticCategory::Broken,
updated_stack.item.id as i32, updated_stack.item.id as i32,
@ -2513,6 +2548,23 @@ impl Player {
/// Sends the world time to only this player. /// Sends the world time to only this player.
pub async fn send_time(&self, world: &World) { 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; let l_world = world.level_time.lock().await;
self.client self.client
.enqueue_packet_editioned( .enqueue_packet_editioned(
@ -2522,6 +2574,94 @@ impl Player {
.await; .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) { pub async fn unload_watched_chunks(&self, world: &World) {
let radial_chunks = self.watched_section.load().all_chunks_within(); let radial_chunks = self.watched_section.load().all_chunks_within();
let level = &world.level; let level = &world.level;
@ -2723,6 +2863,18 @@ impl Player {
} }
pub async fn kick(&self, reason: DisconnectReason, message: TextComponent) { 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; self.client.kick(reason, message).await;
} }
@ -2749,6 +2901,30 @@ impl Player {
self.send_health().await; 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) { pub async fn send_health(&self) {
if !self.has_client_loaded() { if !self.has_client_loaded() {
return; return;
@ -2800,16 +2976,49 @@ impl Player {
self.send_health().await; 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) { pub async fn set_food_level(&self, food_level: u8) {
self.hunger_manager.set_level(food_level); self.hunger_manager.set_level(food_level);
self.send_health().await; self.send_health().await;
} }
pub fn get_saturation(&self) -> f32 {
self.hunger_manager.saturation.load()
}
pub async fn set_saturation(&self, saturation: f32) { pub async fn set_saturation(&self, saturation: f32) {
self.hunger_manager.set_saturation(saturation); self.hunger_manager.set_saturation(saturation);
self.send_health().await; 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 { pub fn get_exhaustion(&self) -> f32 {
self.hunger_manager.get_exhaustion() self.hunger_manager.get_exhaustion()
} }
@ -3258,6 +3467,15 @@ impl Player {
} }
pub async fn swap_item(&self) { 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 (main_hand_item, off_hand_item) = self.inventory.swap_item().await;
let equipment = &[ let equipment = &[
(EquipmentSlot::MAIN_HAND, main_hand_item), (EquipmentSlot::MAIN_HAND, main_hand_item),
@ -3386,6 +3604,18 @@ impl Player {
/// Sets the player's experience level and notifies the client. /// Sets the player's experience level and notifies the client.
pub async fn set_experience(&self, level: i32, progress: f32, points: i32) { 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 // 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_level.store(level, Ordering::Relaxed);
self.experience_progress.store(progress.clamp(0.0, 1.0)); self.experience_progress.store(progress.clamp(0.0, 1.0));
@ -3429,6 +3659,19 @@ impl Player {
self.living_entity.add_effect(effect).await; 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) { pub async fn send_active_effects(&self) {
let effects = self.living_entity.active_effects.lock().await; let effects = self.living_entity.active_effects.lock().await;
for effect in effects.values() { for effect in effects.values() {

View File

@ -1356,6 +1356,18 @@ impl JavaClient {
return; 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 (yaw, pitch) = player.rotation();
let hit_result = player let hit_result = player
.world() .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_combust;
pub mod entity_damage; pub mod entity_damage;
pub mod entity_death; pub mod entity_death;
pub mod entity_dye;
pub mod entity_explode;
pub mod entity_mount;
pub mod entity_regain_health; pub mod entity_regain_health;
pub mod entity_spawn; 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_combust::*;
pub use entity_damage::*; pub use entity_damage::*;
pub use entity_death::*; pub use entity_death::*;
pub use entity_dye::*;
pub use entity_explode::*;
pub use entity_mount::*;
pub use entity_regain_health::*; pub use entity_regain_health::*;
pub use entity_spawn::*; 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 craft_item;
pub mod furnace_burn;
pub mod furnace_smelt; pub mod furnace_smelt;
pub mod inventory_drag; pub mod inventory_drag;
pub mod inventory_open; pub mod inventory_open;
pub mod prepare_anvil;
pub mod trade_select;
pub use brew::*;
pub use craft_item::*; pub use craft_item::*;
pub use furnace_burn::*;
pub use furnace_smelt::*; pub use furnace_smelt::*;
pub use inventory_drag::*; pub use inventory_drag::*;
pub use inventory_open::*; 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_close;
pub mod inventory_interact; pub mod inventory_interact;
pub mod item_held; pub mod item_held;
pub mod player_animation;
pub mod player_bed; pub mod player_bed;
pub mod player_bucket; pub mod player_bucket;
pub mod player_change_world; pub mod player_change_world;
@ -18,14 +19,18 @@ pub mod player_gamemode_change;
pub mod player_interact_entity_event; pub mod player_interact_entity_event;
pub mod player_interact_event; pub mod player_interact_event;
pub mod player_interact_unknown_entity_event; pub mod player_interact_unknown_entity_event;
pub mod player_item_break;
pub mod player_item_consume; pub mod player_item_consume;
pub mod player_item_damage; pub mod player_item_damage;
pub mod player_join; pub mod player_join;
pub mod player_kick;
pub mod player_leave; pub mod player_leave;
pub mod player_level_change;
pub mod player_login; pub mod player_login;
pub mod player_move; pub mod player_move;
pub mod player_permission_check; pub mod player_permission_check;
pub mod player_respawn; pub mod player_respawn;
pub mod player_swap_hands;
pub mod player_teleport; pub mod player_teleport;
pub mod player_toggle_flight_event; pub mod player_toggle_flight_event;
pub mod player_toggle_sneak_event; pub mod player_toggle_sneak_event;
@ -40,6 +45,7 @@ pub use fish::*;
pub use inventory_close::*; pub use inventory_close::*;
pub use inventory_interact::*; pub use inventory_interact::*;
pub use item_held::*; pub use item_held::*;
pub use player_animation::*;
pub use player_bed::*; pub use player_bed::*;
pub use player_bucket::*; pub use player_bucket::*;
pub use player_change_world::*; pub use player_change_world::*;
@ -51,14 +57,18 @@ pub use player_gamemode_change::*;
pub use player_interact_entity_event::*; pub use player_interact_entity_event::*;
pub use player_interact_event::*; pub use player_interact_event::*;
pub use player_interact_unknown_entity_event::*; pub use player_interact_unknown_entity_event::*;
pub use player_item_break::*;
pub use player_item_consume::*; pub use player_item_consume::*;
pub use player_item_damage::*; pub use player_item_damage::*;
pub use player_join::*; pub use player_join::*;
pub use player_kick::*;
pub use player_leave::*; pub use player_leave::*;
pub use player_level_change::*;
pub use player_login::*; pub use player_login::*;
pub use player_move::*; pub use player_move::*;
pub use player_permission_check::*; pub use player_permission_check::*;
pub use player_respawn::*; pub use player_respawn::*;
pub use player_swap_hands::*;
pub use player_teleport::*; pub use player_teleport::*;
pub use player_toggle_flight_event::*; pub use player_toggle_flight_event::*;
pub use player_toggle_sneak_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_load;
pub mod chunk_save; pub mod chunk_save;
pub mod chunk_send; pub mod chunk_send;
pub mod generic_game;
pub mod lightning_strike;
pub mod portal_create;
pub mod spawn_change; pub mod spawn_change;
pub mod structure_grow;
pub mod weather_change; pub mod weather_change;
pub mod world_init;
pub mod world_load; pub mod world_load;
pub use chunk_load::*; pub use chunk_load::*;
pub use chunk_save::*; pub use chunk_save::*;
pub use chunk_send::*; pub use chunk_send::*;
pub use generic_game::*;
pub use lightning_strike::*;
pub use portal_create::*;
pub use spawn_change::*; pub use spawn_change::*;
pub use structure_grow::*;
pub use weather_change::*; pub use weather_change::*;
pub use world_init::*;
pub use world_load::*; 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 context;
pub mod events; pub mod events;
pub mod gui; pub mod gui;
pub mod tab_list;
pub mod title;
use std::{pin::Pin, sync::Arc}; use std::{pin::Pin, sync::Arc};
pub use context::*; pub use context::*;
pub use events::*; pub use events::*;
pub use tab_list::*;
pub use title::*;
/// Struct representing metadata for a plugin. /// 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 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::{ use crate::plugin::loader::wasm::wasm_host::{
state::{EntityResource, PluginHostState}, state::{EntityResource, PluginHostState},
wit::v0_1::events::to_wasm_position, wit::v0_1::events::to_wasm_position,
@ -501,6 +504,32 @@ impl HostEntity for PluginHostState {
Ok(()) 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> { async fn get_eye_height(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_dimension.load().eye_height) Ok(entity.get_entity().entity_dimension.load().eye_height)
@ -804,6 +833,118 @@ impl HostEntity for PluginHostState {
Ok(()) 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( async fn raycast(
&mut self, &mut self,
entity: Resource<Entity>, entity: Resource<Entity>,
@ -848,3 +989,156 @@ impl HostEntity for PluginHostState {
Ok(()) 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()) Some(buf.into())
} }
ClientboundPacket::CDebugSample(data) => { 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 { let p = pumpkin_protocol::java::client::play::CDebugSample {
sample: &sample_vec, sample: &vec_sample,
sample_type: VarInt(data.sample_type), sample_type: VarInt(data.sample_type),
}; };
let mut buf = Vec::new(); let mut buf = Vec::new();

View File

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

View File

@ -868,6 +868,395 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(()) 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( async fn set_tab_list_header_footer(
&mut self, &mut self,
player: Resource<Player>, 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::jukebox::JukeboxBlockEntity as InternalJukeboxBlockEntity;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity as InternalMobSpawnerBlockEntity; use crate::block::entities::mob_spawner::MobSpawnerBlockEntity as InternalMobSpawnerBlockEntity;
use crate::block::entities::sign::SignBlockEntity as InternalSignBlockEntity; 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::{ use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{
BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags, BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags,
BlockPos as WitBlockPos, BlockState as WitBlockState, BoundingBox as WitBoundingBox, BlockPos as WitBlockPos, BlockState as WitBlockState, BoundingBox as WitBoundingBox,
@ -677,6 +678,43 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
Ok(entities) 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( async fn get_block_entity(
&mut self, &mut self,
world: Resource<World>, world: Resource<World>,

View File

@ -448,6 +448,12 @@ impl Server {
new_worlds.push(world.clone()); new_worlds.push(world.clone());
new_worlds 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 world
}) })
.await .await

View File

@ -4729,6 +4729,18 @@ impl World {
self.spawn_entity(item_entity).await; 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 */ /* ItemScatterer.java */
pub async fn scatter_inventory( pub async fn scatter_inventory(
self: &Arc<Self>, self: &Arc<Self>,