feat: extend plugin api
This commit is contained in:
parent
97fdea50d2
commit
0d55aa5411
@ -233,6 +233,9 @@ impl BlockStateId {
|
||||
// depends on generated impl:
|
||||
// pub(crate) const STATE_COUNT: u16;
|
||||
|
||||
/// The total count of all registered block states.
|
||||
pub const COUNT: u16 = Self::STATE_COUNT;
|
||||
|
||||
// SAFETY: There must never be a BlockStateId where self.0 >= BlockStateId::STATE_COUNT
|
||||
|
||||
#[inline]
|
||||
|
||||
@ -252,6 +252,9 @@ impl BlockId {
|
||||
// depends on generated impl:
|
||||
// pub(crate) const BLOCK_COUNT: u16;
|
||||
|
||||
/// The total count of all registered blocks.
|
||||
pub const COUNT: u16 = Self::BLOCK_COUNT;
|
||||
|
||||
// SAFETY: There must never be a BlockId where self.0 >= BlockId::BLOCK_COUNT
|
||||
|
||||
#[inline]
|
||||
|
||||
22
crates/pumpkin-plugin-api/src/events/dialog/dialog_clear.rs
Normal file
22
crates/pumpkin-plugin-api/src/events/dialog/dialog_clear.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::wit::pumpkin::plugin::event::{DialogClearEventData, Event, EventType};
|
||||
|
||||
use super::super::FromIntoEvent;
|
||||
|
||||
/// An event that occurs when a player's dialog is cleared.
|
||||
pub struct DialogClearEvent;
|
||||
|
||||
impl FromIntoEvent for DialogClearEvent {
|
||||
const EVENT_TYPE: EventType = EventType::DialogClearEvent;
|
||||
type Data = DialogClearEventData;
|
||||
|
||||
fn data_from_event(event: Event) -> Self::Data {
|
||||
match event {
|
||||
Event::DialogClearEvent(data) => data,
|
||||
_ => panic!("unexpected event"),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_into_event(data: Self::Data) -> Event {
|
||||
Event::DialogClearEvent(data)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
use crate::wit::pumpkin::plugin::event::{DialogClickActionEventData, Event, EventType};
|
||||
|
||||
use super::super::FromIntoEvent;
|
||||
|
||||
/// An event that occurs when a player clicks a custom dialog button.
|
||||
pub struct DialogClickActionEvent;
|
||||
|
||||
impl FromIntoEvent for DialogClickActionEvent {
|
||||
const EVENT_TYPE: EventType = EventType::DialogClickActionEvent;
|
||||
type Data = DialogClickActionEventData;
|
||||
|
||||
fn data_from_event(event: Event) -> Self::Data {
|
||||
match event {
|
||||
Event::DialogClickActionEvent(data) => data,
|
||||
_ => panic!("unexpected event"),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_into_event(data: Self::Data) -> Event {
|
||||
Event::DialogClickActionEvent(data)
|
||||
}
|
||||
}
|
||||
22
crates/pumpkin-plugin-api/src/events/dialog/dialog_show.rs
Normal file
22
crates/pumpkin-plugin-api/src/events/dialog/dialog_show.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::wit::pumpkin::plugin::event::{DialogShowEventData, Event, EventType};
|
||||
|
||||
use super::super::FromIntoEvent;
|
||||
|
||||
/// An event that occurs when a dialog is shown to a player.
|
||||
pub struct DialogShowEvent;
|
||||
|
||||
impl FromIntoEvent for DialogShowEvent {
|
||||
const EVENT_TYPE: EventType = EventType::DialogShowEvent;
|
||||
type Data = DialogShowEventData;
|
||||
|
||||
fn data_from_event(event: Event) -> Self::Data {
|
||||
match event {
|
||||
Event::DialogShowEvent(data) => data,
|
||||
_ => panic!("unexpected event"),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_into_event(data: Self::Data) -> Event {
|
||||
Event::DialogShowEvent(data)
|
||||
}
|
||||
}
|
||||
10
crates/pumpkin-plugin-api/src/events/dialog/mod.rs
Normal file
10
crates/pumpkin-plugin-api/src/events/dialog/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
/// Dialog clear event.
|
||||
pub mod dialog_clear;
|
||||
/// Dialog click action event.
|
||||
pub mod dialog_click_action;
|
||||
/// Dialog show event.
|
||||
pub mod dialog_show;
|
||||
|
||||
pub use dialog_clear::*;
|
||||
pub use dialog_click_action::*;
|
||||
pub use dialog_show::*;
|
||||
@ -18,6 +18,8 @@ use crate::{Context, Result, Server, wit::pumpkin::plugin::event::EventType};
|
||||
|
||||
/// Block events.
|
||||
pub mod block;
|
||||
/// Dialog events.
|
||||
pub mod dialog;
|
||||
/// Enchantment events.
|
||||
pub mod enchantment;
|
||||
/// Entity events.
|
||||
@ -40,6 +42,7 @@ pub mod vehicle;
|
||||
pub mod world;
|
||||
|
||||
pub use block::*;
|
||||
pub use dialog::*;
|
||||
pub use enchantment::*;
|
||||
pub use entity::*;
|
||||
pub use hanging::*;
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
use crate::wit::pumpkin::plugin::event::{CustomClickActionEventData, Event, EventType};
|
||||
|
||||
use super::super::FromIntoEvent;
|
||||
|
||||
/// An event that occurs when a player clicks a custom dialog button.
|
||||
pub struct CustomClickActionEvent;
|
||||
|
||||
impl FromIntoEvent for CustomClickActionEvent {
|
||||
const EVENT_TYPE: EventType = EventType::CustomClickActionEvent;
|
||||
type Data = CustomClickActionEventData;
|
||||
|
||||
fn data_from_event(event: Event) -> Self::Data {
|
||||
match event {
|
||||
Event::CustomClickActionEvent(data) => data,
|
||||
_ => panic!("unexpected event"),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_into_event(data: Self::Data) -> Event {
|
||||
Event::CustomClickActionEvent(data)
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,6 @@ pub mod async_player_pre_login;
|
||||
pub mod bedrock_form_response;
|
||||
/// Player main hand change event.
|
||||
pub mod changed_main_hand;
|
||||
/// Custom inventory click action event.
|
||||
pub mod custom_click_action;
|
||||
/// Egg throw event.
|
||||
pub mod egg_throw;
|
||||
/// Experience change event.
|
||||
@ -155,7 +153,6 @@ pub use async_player_chat::*;
|
||||
pub use async_player_pre_login::*;
|
||||
pub use bedrock_form_response::*;
|
||||
pub use changed_main_hand::*;
|
||||
pub use custom_click_action::*;
|
||||
pub use egg_throw::*;
|
||||
pub use exp_change::*;
|
||||
pub use fish::*;
|
||||
|
||||
107
crates/pumpkin-plugin-api/src/ext/block.rs
Normal file
107
crates/pumpkin-plugin-api/src/ext/block.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use crate::wit::pumpkin::plugin::world::{
|
||||
Block, BlockState, get_all_block_names, get_all_blocks, get_block_by_id, get_block_by_name,
|
||||
get_block_count, get_block_from_state, get_block_from_state_id, get_block_properties,
|
||||
get_block_state_by_id, get_block_state_count, get_default_state_from_block,
|
||||
get_default_state_from_block_id, get_state_ids_for_block_id, get_states_for_block,
|
||||
get_states_for_block_id,
|
||||
};
|
||||
|
||||
impl Block {
|
||||
/// Returns all registered blocks in the registry.
|
||||
#[must_use]
|
||||
pub fn all() -> Vec<Self> {
|
||||
get_all_blocks()
|
||||
}
|
||||
|
||||
/// Returns the names of all registered blocks.
|
||||
#[must_use]
|
||||
pub fn all_names() -> Vec<String> {
|
||||
get_all_block_names()
|
||||
}
|
||||
|
||||
/// Returns the total number of registered block types.
|
||||
#[must_use]
|
||||
pub fn count() -> u32 {
|
||||
get_block_count()
|
||||
}
|
||||
|
||||
/// Returns the total number of registered block states.
|
||||
#[must_use]
|
||||
pub fn total_state_count() -> u32 {
|
||||
get_block_state_count()
|
||||
}
|
||||
|
||||
/// Gets a block definition by its numerical block ID.
|
||||
#[must_use]
|
||||
pub fn from_id(id: u16) -> Option<Self> {
|
||||
get_block_by_id(id)
|
||||
}
|
||||
|
||||
/// Gets a block definition by its namespaced name (e.g., "minecraft:stone" or "stone").
|
||||
#[must_use]
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
get_block_by_name(name)
|
||||
}
|
||||
|
||||
/// Gets the block definition for a given block state ID.
|
||||
#[must_use]
|
||||
pub fn from_state_id(state_id: u16) -> Option<Self> {
|
||||
get_block_from_state_id(state_id)
|
||||
}
|
||||
|
||||
/// Gets the block definition for a given block state.
|
||||
#[must_use]
|
||||
pub fn from_state(state: &BlockState) -> Self {
|
||||
get_block_from_state(state)
|
||||
}
|
||||
|
||||
/// Gets all valid block states for this block type.
|
||||
#[must_use]
|
||||
pub fn get_states(&self) -> Vec<BlockState> {
|
||||
get_states_for_block(self)
|
||||
}
|
||||
|
||||
/// Gets all valid block states for a given numerical block ID.
|
||||
#[must_use]
|
||||
pub fn get_states_for_id(block_id: u16) -> Vec<BlockState> {
|
||||
get_states_for_block_id(block_id)
|
||||
}
|
||||
|
||||
/// Gets all valid block state IDs for a given numerical block ID.
|
||||
#[must_use]
|
||||
pub fn get_state_ids_for_id(block_id: u16) -> Vec<u16> {
|
||||
get_state_ids_for_block_id(block_id)
|
||||
}
|
||||
|
||||
/// Gets the default block state for this block.
|
||||
#[must_use]
|
||||
pub fn get_default_state(&self) -> BlockState {
|
||||
get_default_state_from_block(self)
|
||||
}
|
||||
|
||||
/// Gets the default block state for a given numerical block ID.
|
||||
#[must_use]
|
||||
pub fn get_default_state_for_id(block_id: u16) -> Option<BlockState> {
|
||||
get_default_state_from_block_id(block_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockState {
|
||||
/// Gets the parent block definition for this block state.
|
||||
#[must_use]
|
||||
pub fn get_block(&self) -> Block {
|
||||
get_block_from_state(self)
|
||||
}
|
||||
|
||||
/// Gets a detailed block state by its numerical block state ID.
|
||||
#[must_use]
|
||||
pub fn from_id(state_id: u16) -> Option<Self> {
|
||||
get_block_state_by_id(state_id)
|
||||
}
|
||||
|
||||
/// Gets property key-value pairs for this block state.
|
||||
#[must_use]
|
||||
pub fn get_properties(&self) -> Vec<(String, String)> {
|
||||
get_block_properties(self.id)
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
mod advancement;
|
||||
mod attributes;
|
||||
mod block;
|
||||
mod game_rules;
|
||||
pub mod player;
|
||||
mod server_list_ping;
|
||||
|
||||
@ -145,7 +145,10 @@ pub use wit::pumpkin::plugin::item_stack::ItemStack;
|
||||
pub use wit::pumpkin::plugin::player::Player;
|
||||
pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
|
||||
pub use wit::pumpkin::plugin::server::Dimension;
|
||||
pub use wit::pumpkin::plugin::world::World;
|
||||
pub use wit::pumpkin::plugin::world::{
|
||||
Block, BlockDirection, BlockState, BlockStateInfo, Entity, Flammable, RayTraceBlockResult,
|
||||
RayTraceEntityResult, RaycastResult, World, WorldBorder,
|
||||
};
|
||||
pub use worldgen::{ChunkBuffer, ChunkGenerator, GenerationPhase, GeneratorManager};
|
||||
|
||||
/// Advancement WIT API re-exports.
|
||||
@ -157,7 +160,11 @@ pub mod advancement {
|
||||
|
||||
/// Java dialog WIT API re-exports.
|
||||
pub mod java_dialog {
|
||||
pub use crate::wit::pumpkin::plugin::java_dialogs::{ActionButton, DialogBody, DialogType};
|
||||
pub use crate::wit::pumpkin::plugin::java_dialogs::{
|
||||
Action, ActionButton, AfterAction, CustomClickAction, Dialog, DialogBody, DialogInput,
|
||||
DialogInputBool, DialogInputNumberRange, DialogInputSingleOption, DialogInputText,
|
||||
DialogType, Link, LinkLabel, LinkType,
|
||||
};
|
||||
}
|
||||
|
||||
/// WIT-based logging subscriber.
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2337e6f389293a6371667d495feace8e35570318
|
||||
Subproject commit 060c913c3097ae730fb2c04b7835733dcc85a1a4
|
||||
@ -39,6 +39,7 @@ pub enum DialogNBTSource<'a> {
|
||||
Nbt(&'a pumpkin_nbt::compound::NbtCompound),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dialog {
|
||||
pub r#type: String,
|
||||
pub title: TextComponent,
|
||||
@ -52,11 +53,13 @@ pub struct Dialog {
|
||||
pub external_title: Option<TextComponent>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DialogBody {
|
||||
PlainMessage { contents: TextComponent },
|
||||
Item { item: i32 }, // TODO: ItemStack serialization to NBT
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DialogInput {
|
||||
Boolean {
|
||||
label: TextComponent,
|
||||
@ -82,6 +85,7 @@ pub enum DialogInput {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ActionButton {
|
||||
pub text: TextComponent,
|
||||
pub tooltip: Option<TextComponent>,
|
||||
@ -89,6 +93,7 @@ pub struct ActionButton {
|
||||
pub action: DialogAction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DialogAction {
|
||||
OpenUrl {
|
||||
url: String,
|
||||
@ -99,6 +104,7 @@ pub enum DialogAction {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DialogLink {
|
||||
pub label: crate::Label,
|
||||
pub url: String,
|
||||
|
||||
@ -483,6 +483,7 @@ impl PositionFlag {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Label {
|
||||
BuiltIn(LinkType),
|
||||
TextComponent(Box<TextComponent>),
|
||||
@ -523,7 +524,7 @@ impl<'a> Link<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum LinkType {
|
||||
BugReport = 0,
|
||||
|
||||
@ -1383,6 +1383,47 @@ impl TextComponent {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new text component displaying the name of one or more entities found by a selector.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `selector` – The entity selector string (e.g. `@e[type=pig]`).
|
||||
/// - `separator` – Optional separator string between multiple entity names.
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `TextComponent` displaying entity names.
|
||||
#[must_use]
|
||||
pub fn entity_names<S: Into<Cow<'static, str>>, P: Into<Cow<'static, str>>>(
|
||||
selector: S,
|
||||
separator: Option<P>,
|
||||
) -> Self {
|
||||
Self(TextComponentBase {
|
||||
content: Box::new(TextContent::EntityNames {
|
||||
selector: selector.into(),
|
||||
separator: separator.map(Into::into),
|
||||
}),
|
||||
style: Box::new(Style::default()),
|
||||
extra: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new text component displaying a keybind identifier.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `keybind` – The keybind identifier (e.g. `key.jump`, `key.forward`).
|
||||
///
|
||||
/// # Returns
|
||||
/// A new `TextComponent` displaying the configured key.
|
||||
#[must_use]
|
||||
pub fn keybind<K: Into<Cow<'static, str>>>(keybind: K) -> Self {
|
||||
Self(TextComponentBase {
|
||||
content: Box::new(TextContent::Keybind {
|
||||
keybind: keybind.into(),
|
||||
}),
|
||||
style: Box::new(Style::default()),
|
||||
extra: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Appends a child component to this component.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@ -160,6 +160,66 @@ async fn get_hinge(
|
||||
#[pumpkin_block_from_tag("minecraft:doors")]
|
||||
pub struct DoorBlock;
|
||||
|
||||
impl DoorBlock {
|
||||
#[must_use]
|
||||
pub fn is_wooden_door(world: &World, block_pos: &BlockPos) -> bool {
|
||||
let block = world.get_block(block_pos);
|
||||
block.has_tag(&tag::Block::MINECRAFT_WOODEN_DOORS)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_open(world: &World, block_pos: &BlockPos) -> bool {
|
||||
let (block, block_state) = world.get_block_and_state_id(block_pos);
|
||||
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
|
||||
return false;
|
||||
}
|
||||
let door_props = DoorProperties::from_state_id(block_state, block);
|
||||
door_props.open
|
||||
}
|
||||
|
||||
pub async fn set_open(world: &Arc<World>, block_pos: &BlockPos, open: bool) {
|
||||
let (block, block_state) = world.get_block_and_state_id(block_pos);
|
||||
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
|
||||
return;
|
||||
}
|
||||
let mut door_props = DoorProperties::from_state_id(block_state, block);
|
||||
if door_props.open == open {
|
||||
return;
|
||||
}
|
||||
door_props.open = open;
|
||||
|
||||
let other_half = match door_props.half {
|
||||
DoubleBlockHalf::Upper => BlockDirection::Down,
|
||||
DoubleBlockHalf::Lower => BlockDirection::Up,
|
||||
};
|
||||
let other_pos = block_pos.offset(other_half.to_offset());
|
||||
|
||||
let (other_block, other_state_id) = world.get_block_and_state_id(&other_pos);
|
||||
|
||||
world.play_block_sound(get_sound(block, open), SoundCategory::Blocks, *block_pos);
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
block_pos,
|
||||
door_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
|
||||
if other_block.id == block.id {
|
||||
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
|
||||
other_door_props.open = open;
|
||||
world
|
||||
.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehaviour for DoorBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
|
||||
@ -14,4 +14,12 @@ pub trait Control: Send + Sync {
|
||||
|
||||
pub trait MoveControlTrait: Control {
|
||||
fn tick(&mut self, mob: &dyn Mob);
|
||||
|
||||
fn set_wanted_position(&mut self, _x: f64, _y: f64, _z: f64, _speed_modifier: f64) {}
|
||||
|
||||
fn strafe(&mut self, _forward: f32, _right: f32) {}
|
||||
|
||||
fn has_wanted(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +101,27 @@ impl MoveControlTrait for MoveControl {
|
||||
|
||||
// Navigator owns movement input while this controller waits.
|
||||
}
|
||||
|
||||
fn set_wanted_position(&mut self, x: f64, y: f64, z: f64, speed_modifier: f64) {
|
||||
self.wanted_x = x;
|
||||
self.wanted_y = y;
|
||||
self.wanted_z = z;
|
||||
self.speed_modifier = speed_modifier;
|
||||
if self.operation != Operation::Jumping {
|
||||
self.operation = Operation::MoveTo;
|
||||
}
|
||||
}
|
||||
|
||||
fn strafe(&mut self, forwards: f32, right: f32) {
|
||||
self.operation = Operation::Strafe;
|
||||
self.strafe_forwards = forwards;
|
||||
self.strafe_right = right;
|
||||
self.speed_modifier = 0.25;
|
||||
}
|
||||
|
||||
fn has_wanted(&self) -> bool {
|
||||
self.operation == Operation::MoveTo
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveControl {
|
||||
|
||||
228
crates/pumpkin/src/entity/ai/goal/break_door.rs
Normal file
228
crates/pumpkin/src/entity/ai/goal/break_door.rs
Normal file
@ -0,0 +1,228 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_util::Difficulty;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use rand::RngExt;
|
||||
|
||||
use super::door_interact::DoorInteractGoal;
|
||||
use super::{Controls, Goal, GoalFuture};
|
||||
use crate::block::blocks::doors::DoorBlock;
|
||||
use crate::entity::mob::Mob;
|
||||
|
||||
const DEFAULT_DOOR_BREAK_TIME: i32 = 240;
|
||||
|
||||
pub type DifficultyPredicate = Arc<dyn Fn(Difficulty) -> bool + Send + Sync>;
|
||||
|
||||
pub struct BreakDoorGoal {
|
||||
pub door_interact_goal: DoorInteractGoal,
|
||||
valid_difficulties: DifficultyPredicate,
|
||||
pub break_time: i32,
|
||||
pub last_break_progress: i32,
|
||||
pub door_break_time: i32,
|
||||
}
|
||||
|
||||
impl BreakDoorGoal {
|
||||
#[must_use]
|
||||
pub fn new(valid_difficulties: DifficultyPredicate) -> Self {
|
||||
Self {
|
||||
door_interact_goal: DoorInteractGoal::new(),
|
||||
valid_difficulties,
|
||||
break_time: 0,
|
||||
last_break_progress: -1,
|
||||
door_break_time: -1,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_door_break_time(
|
||||
door_break_time: i32,
|
||||
valid_difficulties: DifficultyPredicate,
|
||||
) -> Self {
|
||||
Self {
|
||||
door_interact_goal: DoorInteractGoal::new(),
|
||||
valid_difficulties,
|
||||
break_time: 0,
|
||||
last_break_progress: -1,
|
||||
door_break_time,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_door_break_time(&self) -> i32 {
|
||||
if self.door_break_time > DEFAULT_DOOR_BREAK_TIME {
|
||||
self.door_break_time
|
||||
} else {
|
||||
DEFAULT_DOOR_BREAK_TIME
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid_difficulty(&self, difficulty: Difficulty) -> bool {
|
||||
(self.valid_difficulties)(difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BreakDoorGoal {
|
||||
fn default() -> Self {
|
||||
Self::new(Arc::new(|d| d == Difficulty::Hard))
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for BreakDoorGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
if !self.door_interact_goal.can_use(mob) {
|
||||
return false;
|
||||
}
|
||||
let world = mob.get_entity().world.load();
|
||||
let level_info = world.level_info.load();
|
||||
if !level_info.game_rules.mob_griefing {
|
||||
return false;
|
||||
}
|
||||
self.is_valid_difficulty(level_info.difficulty) && !self.door_interact_goal.is_open(mob)
|
||||
})
|
||||
}
|
||||
|
||||
fn should_continue<'a>(&'a self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let world = mob.get_entity().world.load();
|
||||
let level_info = world.level_info.load();
|
||||
let mob_pos = mob.get_entity().pos.load();
|
||||
let door_pos = self.door_interact_goal.door_pos;
|
||||
let center_x = f64::from(door_pos.0.x) + 0.5;
|
||||
let center_y = f64::from(door_pos.0.y) + 0.5;
|
||||
let center_z = f64::from(door_pos.0.z) + 0.5;
|
||||
let dx = center_x - mob_pos.x;
|
||||
let dy = center_y - mob_pos.y;
|
||||
let dz = center_z - mob_pos.z;
|
||||
let dist_sq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
self.break_time <= self.get_door_break_time()
|
||||
&& !DoorBlock::is_open(&world, &door_pos)
|
||||
&& dist_sq < 4.0
|
||||
&& self.is_valid_difficulty(level_info.difficulty)
|
||||
})
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.door_interact_goal.start_interaction(mob);
|
||||
self.break_time = 0;
|
||||
self.last_break_progress = -1;
|
||||
})
|
||||
}
|
||||
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let world = mob.get_entity().world.load();
|
||||
world.set_block_destroy_stage(
|
||||
mob.get_entity().entity_id,
|
||||
self.door_interact_goal.door_pos,
|
||||
-1,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.door_interact_goal.tick_interaction(mob);
|
||||
let world = mob.get_entity().world.load_full();
|
||||
|
||||
if mob.get_random().random_range(0..20) == 0 {
|
||||
world.sync_world_event(
|
||||
WorldEvent::SoundZombieWoodenDoor,
|
||||
self.door_interact_goal.door_pos,
|
||||
0,
|
||||
);
|
||||
mob.get_mob_entity().living_entity.swing_hand().await;
|
||||
}
|
||||
|
||||
self.break_time += 1;
|
||||
let progress =
|
||||
(self.break_time as f32 / self.get_door_break_time() as f32 * 10.0) as i32;
|
||||
if progress != self.last_break_progress {
|
||||
world.set_block_destroy_stage(
|
||||
mob.get_entity().entity_id,
|
||||
self.door_interact_goal.door_pos,
|
||||
progress as i8,
|
||||
);
|
||||
self.last_break_progress = progress;
|
||||
}
|
||||
|
||||
let level_info = world.level_info.load();
|
||||
if self.break_time == self.get_door_break_time()
|
||||
&& self.is_valid_difficulty(level_info.difficulty)
|
||||
{
|
||||
let (_, block_state_id) =
|
||||
world.get_block_and_state_id(&self.door_interact_goal.door_pos);
|
||||
mob.break_door(self.door_interact_goal.door_pos).await;
|
||||
world
|
||||
.set_block_state(
|
||||
&self.door_interact_goal.door_pos,
|
||||
BlockStateId::AIR,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.sync_world_event(
|
||||
WorldEvent::SoundZombieDoorCrash,
|
||||
self.door_interact_goal.door_pos,
|
||||
0,
|
||||
);
|
||||
world.sync_world_event(
|
||||
WorldEvent::ParticlesDestroyBlock,
|
||||
self.door_interact_goal.door_pos,
|
||||
i32::from(block_state_id.as_u16()),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn should_run_every_tick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
Controls::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn door_break_time_default_and_custom() {
|
||||
let default_goal = BreakDoorGoal::default();
|
||||
assert_eq!(default_goal.get_door_break_time(), 240);
|
||||
|
||||
let custom_goal =
|
||||
BreakDoorGoal::with_door_break_time(300, Arc::new(|d| d == Difficulty::Hard));
|
||||
assert_eq!(custom_goal.get_door_break_time(), 300);
|
||||
|
||||
let low_goal =
|
||||
BreakDoorGoal::with_door_break_time(100, Arc::new(|d| d == Difficulty::Hard));
|
||||
assert_eq!(low_goal.get_door_break_time(), 240);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_difficulty() {
|
||||
let hard_only = BreakDoorGoal::new(Arc::new(|d| d == Difficulty::Hard));
|
||||
assert!(hard_only.is_valid_difficulty(Difficulty::Hard));
|
||||
assert!(!hard_only.is_valid_difficulty(Difficulty::Normal));
|
||||
assert!(!hard_only.is_valid_difficulty(Difficulty::Easy));
|
||||
assert!(!hard_only.is_valid_difficulty(Difficulty::Peaceful));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_progress_calculation() {
|
||||
let break_time = 120;
|
||||
let total_time = 240;
|
||||
let progress = (break_time as f32 / total_time as f32 * 10.0) as i32;
|
||||
assert_eq!(progress, 5);
|
||||
|
||||
let end_progress = (240.0f32 / 240.0f32 * 10.0) as i32;
|
||||
assert_eq!(end_progress, 10);
|
||||
}
|
||||
}
|
||||
201
crates/pumpkin/src/entity/ai/goal/door_interact.rs
Normal file
201
crates/pumpkin/src/entity/ai/goal/door_interact.rs
Normal file
@ -0,0 +1,201 @@
|
||||
use pumpkin_data::tag::{self, Taggable};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use super::{Controls, Goal, GoalFuture};
|
||||
use crate::block::blocks::doors::DoorBlock;
|
||||
use crate::entity::mob::Mob;
|
||||
|
||||
pub struct DoorInteractGoal {
|
||||
pub door_pos: BlockPos,
|
||||
pub has_door: bool,
|
||||
pub passed: bool,
|
||||
pub door_open_dir_x: f32,
|
||||
pub door_open_dir_z: f32,
|
||||
}
|
||||
|
||||
impl Default for DoorInteractGoal {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl DoorInteractGoal {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
door_pos: BlockPos::ZERO,
|
||||
has_door: false,
|
||||
passed: false,
|
||||
door_open_dir_x: 0.0,
|
||||
door_open_dir_z: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_open(&mut self, mob: &dyn Mob) -> bool {
|
||||
if !self.has_door {
|
||||
return false;
|
||||
}
|
||||
let world = mob.get_entity().world.load();
|
||||
let (block, _) = world.get_block_and_state_id(&self.door_pos);
|
||||
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
|
||||
self.has_door = false;
|
||||
return false;
|
||||
}
|
||||
DoorBlock::is_open(&world, &self.door_pos)
|
||||
}
|
||||
|
||||
pub async fn set_open(&mut self, mob: &dyn Mob, open: bool) {
|
||||
if self.has_door {
|
||||
let world = mob.get_entity().world.load_full();
|
||||
let (block, _) = world.get_block_and_state_id(&self.door_pos);
|
||||
if block.has_tag(&tag::Block::MINECRAFT_DOORS) {
|
||||
DoorBlock::set_open(&world, &self.door_pos, open).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_use(&mut self, mob: &dyn Mob) -> bool {
|
||||
if !mob
|
||||
.get_entity()
|
||||
.horizontal_collision
|
||||
.load(Ordering::Relaxed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let navigator = mob
|
||||
.get_mob_entity()
|
||||
.navigator
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
let Some(path) = navigator.get_path() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if path.is_done() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let world = mob.get_entity().world.load();
|
||||
let mob_pos = mob.get_entity().pos.load();
|
||||
let limit = (path.get_next_node_index() + 2).min(path.get_node_count());
|
||||
|
||||
for i in 0..limit {
|
||||
let Some(node) = path.get_node(i) else {
|
||||
continue;
|
||||
};
|
||||
let door_pos = BlockPos::new(node.pos.0.x, node.pos.0.y + 1, node.pos.0.z);
|
||||
let dx = mob_pos.x - f64::from(door_pos.0.x);
|
||||
let dz = mob_pos.z - f64::from(door_pos.0.z);
|
||||
let dist_sqr = dx * dx + dz * dz;
|
||||
|
||||
if dist_sqr <= 2.25 {
|
||||
self.door_pos = door_pos;
|
||||
self.has_door = DoorBlock::is_wooden_door(&world, &self.door_pos);
|
||||
if self.has_door {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let above_pos = mob.get_entity().block_pos.load().up();
|
||||
self.door_pos = above_pos;
|
||||
self.has_door = DoorBlock::is_wooden_door(&world, &self.door_pos);
|
||||
self.has_door
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn can_continue_to_use(&self) -> bool {
|
||||
!self.passed
|
||||
}
|
||||
|
||||
pub fn start_interaction(&mut self, mob: &dyn Mob) {
|
||||
self.passed = false;
|
||||
let mob_pos = mob.get_entity().pos.load();
|
||||
self.door_open_dir_x = (self.door_pos.0.x as f32 + 0.5) - mob_pos.x as f32;
|
||||
self.door_open_dir_z = (self.door_pos.0.z as f32 + 0.5) - mob_pos.z as f32;
|
||||
}
|
||||
|
||||
pub fn tick_interaction(&mut self, mob: &dyn Mob) {
|
||||
let mob_pos = mob.get_entity().pos.load();
|
||||
let new_door_dir_x = (self.door_pos.0.x as f32 + 0.5) - mob_pos.x as f32;
|
||||
let new_door_dir_z = (self.door_pos.0.z as f32 + 0.5) - mob_pos.z as f32;
|
||||
let dot = self.door_open_dir_x * new_door_dir_x + self.door_open_dir_z * new_door_dir_z;
|
||||
if dot < 0.0 {
|
||||
self.passed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for DoorInteractGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move { self.can_use(mob) })
|
||||
}
|
||||
|
||||
fn should_continue<'a>(&'a self, _mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move { self.can_continue_to_use() })
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.start_interaction(mob);
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.tick_interaction(mob);
|
||||
})
|
||||
}
|
||||
|
||||
fn should_run_every_tick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
Controls::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn passed_when_crossing_door() {
|
||||
let mut goal = DoorInteractGoal::new();
|
||||
goal.door_pos = BlockPos::new(10, 64, 10);
|
||||
|
||||
// Simulate start: mob at (10.5, 64.0, 8.0), moving towards door at Z=10.5
|
||||
// door_open_dir = (10.5 - 10.5, 10.5 - 8.0) = (0.0, 2.5)
|
||||
goal.door_open_dir_x = 0.0;
|
||||
goal.door_open_dir_z = 2.5;
|
||||
goal.passed = false;
|
||||
|
||||
assert!(goal.can_continue_to_use());
|
||||
|
||||
// Still before door: mob at (10.5, 64.0, 9.5) -> new_door_dir = (0.0, 1.0)
|
||||
// dot = 0*0 + 2.5*1.0 = 2.5 > 0
|
||||
let new_x = 0.0f32;
|
||||
let new_z = 1.0f32;
|
||||
let dot = goal.door_open_dir_x * new_x + goal.door_open_dir_z * new_z;
|
||||
if dot < 0.0 {
|
||||
goal.passed = true;
|
||||
}
|
||||
assert!(!goal.passed);
|
||||
assert!(goal.can_continue_to_use());
|
||||
|
||||
// Crossed door: mob at (10.5, 64.0, 12.0) -> new_door_dir = (0.0, -1.5)
|
||||
// dot = 0*0 + 2.5*(-1.5) = -3.75 < 0
|
||||
let new_x2 = 0.0f32;
|
||||
let new_z2 = -1.5f32;
|
||||
let dot2 = goal.door_open_dir_x * new_x2 + goal.door_open_dir_z * new_z2;
|
||||
if dot2 < 0.0 {
|
||||
goal.passed = true;
|
||||
}
|
||||
assert!(goal.passed);
|
||||
assert!(!goal.can_continue_to_use());
|
||||
}
|
||||
}
|
||||
@ -23,32 +23,40 @@ impl GoalSelector {
|
||||
}
|
||||
|
||||
pub async fn remove_goal<G: Goal + 'static>(&mut self, mob: &dyn Mob) {
|
||||
let mut goals_to_remove = Vec::with_capacity(2);
|
||||
for (i, prioritized_goal) in &mut self.goals.iter_mut().enumerate() {
|
||||
if TypeId::of::<G>() == prioritized_goal.type_id {
|
||||
if prioritized_goal.running {
|
||||
prioritized_goal.stop(mob).await;
|
||||
}
|
||||
goals_to_remove.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
for goal_idx in goals_to_remove {
|
||||
self.goals.swap_remove(goal_idx);
|
||||
|
||||
// This is very fast because arrays are on the stack and the compiler knows the size
|
||||
for slot in &mut self.goals_by_control {
|
||||
if *slot == usize::MAX {
|
||||
continue;
|
||||
}
|
||||
// Update the idx
|
||||
if *slot == goal_idx {
|
||||
*slot = usize::MAX;
|
||||
} else if *slot > goal_idx {
|
||||
*slot -= 1;
|
||||
let mut stopped = self.remove_goal_sync::<G>();
|
||||
for goal in &mut stopped {
|
||||
goal.stop(mob).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_goal_sync<G: Goal + 'static>(&mut self) -> Vec<PrioritizedGoal> {
|
||||
self.remove_goal_by_type_id(TypeId::of::<G>())
|
||||
}
|
||||
|
||||
pub fn remove_goal_by_type_id(&mut self, type_id: TypeId) -> Vec<PrioritizedGoal> {
|
||||
let mut stopped = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < self.goals.len() {
|
||||
if self.goals[i].type_id == type_id {
|
||||
let goal = self.goals.swap_remove(i);
|
||||
for slot in &mut self.goals_by_control {
|
||||
if *slot == usize::MAX {
|
||||
continue;
|
||||
}
|
||||
if *slot == i {
|
||||
*slot = usize::MAX;
|
||||
} else if *slot == self.goals.len() {
|
||||
*slot = i;
|
||||
}
|
||||
}
|
||||
if goal.running {
|
||||
stopped.push(goal);
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
stopped
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> Vec<PrioritizedGoal> {
|
||||
|
||||
@ -7,10 +7,12 @@ pub mod avoid_entity;
|
||||
pub mod beg;
|
||||
pub mod blaze_attack;
|
||||
pub mod bow_attack;
|
||||
pub mod break_door;
|
||||
pub mod breed;
|
||||
pub mod chase_player;
|
||||
pub mod creeper_ignite;
|
||||
pub mod destroy_egg;
|
||||
pub mod door_interact;
|
||||
pub mod eat_grass;
|
||||
pub mod escape_danger;
|
||||
pub mod follow_owner;
|
||||
@ -20,6 +22,8 @@ pub mod look_around;
|
||||
pub mod look_at_entity;
|
||||
pub mod melee_attack;
|
||||
pub mod move_to_target_pos;
|
||||
pub mod offer_flower;
|
||||
pub mod open_door;
|
||||
pub mod owner_hurt_by_target;
|
||||
pub mod owner_hurt_target;
|
||||
pub mod pathfind_to_raid;
|
||||
@ -33,6 +37,7 @@ pub mod teleport_towards_player;
|
||||
pub mod tempt;
|
||||
pub(crate) mod track_target;
|
||||
pub mod trade_with_player;
|
||||
pub mod try_find_water;
|
||||
pub mod wander_around;
|
||||
pub mod work_at_job_site;
|
||||
pub mod zombie_attack;
|
||||
|
||||
185
crates/pumpkin/src/entity/ai/goal/offer_flower.rs
Normal file
185
crates/pumpkin/src/entity/ai/goal/offer_flower.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use std::ops::BitOr;
|
||||
|
||||
use pumpkin_data::entity::EntityStatus;
|
||||
use pumpkin_data::tag::{self, Taggable};
|
||||
use rand::RngExt;
|
||||
|
||||
use super::{Controls, Goal, GoalFuture};
|
||||
use crate::entity::mob::Mob;
|
||||
|
||||
pub const OFFER_TICKS: i32 = 400;
|
||||
|
||||
pub struct OfferFlowerGoal {
|
||||
pub target_entity_id: Option<i32>,
|
||||
pub tick: i32,
|
||||
}
|
||||
|
||||
impl Default for OfferFlowerGoal {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl OfferFlowerGoal {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
target_entity_id: None,
|
||||
tick: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for OfferFlowerGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let world = mob.get_entity().world.load();
|
||||
if world.level_time.lock().await.is_night() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if mob.get_random().random_range(0..8000) != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let golem_entity = mob.get_entity();
|
||||
let golem_pos = golem_entity.pos.load();
|
||||
let bb = golem_entity.bounding_box.load().expand(6.0, 2.0, 6.0);
|
||||
let nearby = world.get_entities_at_box(&bb);
|
||||
|
||||
let mut closest: Option<(i32, f64)> = None;
|
||||
|
||||
for candidate in nearby {
|
||||
let cand_entity = candidate.get_entity();
|
||||
if cand_entity.entity_id == golem_entity.entity_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !cand_entity
|
||||
.entity_type
|
||||
.has_tag(&tag::EntityType::MINECRAFT_CANDIDATE_FOR_IRON_GOLEM_GIFT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let cand_pos = cand_entity.pos.load();
|
||||
let dx = cand_pos.x - golem_pos.x;
|
||||
let dy = cand_pos.y - golem_pos.y;
|
||||
let dz = cand_pos.z - golem_pos.z;
|
||||
let dist_sq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if dist_sq <= 36.0 {
|
||||
if let Some((_, closest_dist)) = closest {
|
||||
if dist_sq < closest_dist {
|
||||
closest = Some((cand_entity.entity_id, dist_sq));
|
||||
}
|
||||
} else {
|
||||
closest = Some((cand_entity.entity_id, dist_sq));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((id, _)) = closest {
|
||||
self.target_entity_id = Some(id);
|
||||
true
|
||||
} else {
|
||||
self.target_entity_id = None;
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn should_continue<'a>(&'a self, _mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move { self.tick > 0 })
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.tick = OFFER_TICKS;
|
||||
if let Some(golem) = mob.as_iron_golem() {
|
||||
golem.offer_flower(true);
|
||||
} else {
|
||||
let entity = mob.get_entity();
|
||||
let world = entity.world.load();
|
||||
world.send_entity_status(entity, EntityStatus::OfferFlower, None);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(golem) = mob.as_iron_golem() {
|
||||
golem.offer_flower(false);
|
||||
} else {
|
||||
let entity = mob.get_entity();
|
||||
let world = entity.world.load();
|
||||
world.send_entity_status(entity, EntityStatus::StopOfferFlower, None);
|
||||
}
|
||||
|
||||
if self.tick == 0
|
||||
&& let Some(target_id) = self.target_entity_id
|
||||
{
|
||||
let world = mob.get_entity().world.load();
|
||||
if let Some(target) = world.get_entity_by_id(target_id) {
|
||||
let target_entity = target.get_entity();
|
||||
let bb = mob.get_entity().bounding_box.load().expand(6.0, 2.0, 6.0);
|
||||
if target_entity
|
||||
.entity_type
|
||||
.has_tag(&tag::EntityType::MINECRAFT_ACCEPTS_IRON_GOLEM_GIFT)
|
||||
&& bb.intersects(&target_entity.bounding_box.load())
|
||||
{
|
||||
// Target accepted gift
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.target_entity_id = None;
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(target_id) = self.target_entity_id {
|
||||
let world = mob.get_entity().world.load();
|
||||
if let Some(target) = world.get_entity_by_id(target_id) {
|
||||
let target_entity = target.get_entity();
|
||||
let target_pos = target_entity.pos.load();
|
||||
mob.get_mob_entity()
|
||||
.look_control
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.look_at_with_range(
|
||||
target_pos.x,
|
||||
target_entity.get_eye_y(),
|
||||
target_pos.z,
|
||||
30.0,
|
||||
30.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.tick -= 1;
|
||||
})
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
Controls::MOVE.bitor(Controls::LOOK)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn offer_flower_goal_lifecycle() {
|
||||
let mut goal = OfferFlowerGoal::new();
|
||||
assert_eq!(goal.tick, 0);
|
||||
assert!(goal.target_entity_id.is_none());
|
||||
|
||||
goal.tick = OFFER_TICKS;
|
||||
assert_eq!(goal.tick, 400);
|
||||
let controls = goal.controls();
|
||||
assert!(controls.get(Controls::MOVE));
|
||||
assert!(controls.get(Controls::LOOK));
|
||||
}
|
||||
}
|
||||
97
crates/pumpkin/src/entity/ai/goal/open_door.rs
Normal file
97
crates/pumpkin/src/entity/ai/goal/open_door.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use super::door_interact::DoorInteractGoal;
|
||||
use super::{Controls, Goal, GoalFuture};
|
||||
use crate::entity::mob::Mob;
|
||||
|
||||
pub struct OpenDoorGoal {
|
||||
pub door_interact_goal: DoorInteractGoal,
|
||||
pub close_door: bool,
|
||||
pub forget_time: i32,
|
||||
}
|
||||
|
||||
impl OpenDoorGoal {
|
||||
#[must_use]
|
||||
pub const fn new(close_door_after: bool) -> Self {
|
||||
Self {
|
||||
door_interact_goal: DoorInteractGoal::new(),
|
||||
close_door: close_door_after,
|
||||
forget_time: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OpenDoorGoal {
|
||||
fn default() -> Self {
|
||||
Self::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for OpenDoorGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move { self.door_interact_goal.can_use(mob) })
|
||||
}
|
||||
|
||||
fn should_continue<'a>(&'a self, _mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
self.close_door && self.forget_time > 0 && self.door_interact_goal.can_continue_to_use()
|
||||
})
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.door_interact_goal.start_interaction(mob);
|
||||
self.forget_time = 20;
|
||||
self.door_interact_goal.set_open(mob, true).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if self.close_door {
|
||||
self.door_interact_goal.set_open(mob, false).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.forget_time -= 1;
|
||||
self.door_interact_goal.tick_interaction(mob);
|
||||
})
|
||||
}
|
||||
|
||||
fn should_run_every_tick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
Controls::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn open_door_can_continue_to_use() {
|
||||
let mut goal = OpenDoorGoal::new(true);
|
||||
goal.forget_time = 20;
|
||||
goal.door_interact_goal.passed = false;
|
||||
|
||||
assert!(goal.close_door);
|
||||
assert!(goal.forget_time > 0);
|
||||
assert!(goal.door_interact_goal.can_continue_to_use());
|
||||
|
||||
// When forget_time expires
|
||||
goal.forget_time = 0;
|
||||
assert!(
|
||||
!(goal.close_door
|
||||
&& goal.forget_time > 0
|
||||
&& goal.door_interact_goal.can_continue_to_use())
|
||||
);
|
||||
|
||||
// When close_door is false
|
||||
let goal_no_close = OpenDoorGoal::new(false);
|
||||
assert!(!goal_no_close.close_door);
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ impl OwnerHurtByTargetGoal {
|
||||
impl Goal for OwnerHurtByTargetGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async {
|
||||
if mob.is_sitting() {
|
||||
if !mob.is_tamed() || mob.is_sitting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -105,3 +105,14 @@ impl Goal for OwnerHurtByTargetGoal {
|
||||
Controls::TARGET
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn owner_hurt_by_target_goal_controls() {
|
||||
let goal = OwnerHurtByTargetGoal::new();
|
||||
assert!(goal.controls().get(Controls::TARGET));
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ impl OwnerHurtTargetGoal {
|
||||
impl Goal for OwnerHurtTargetGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async {
|
||||
if mob.is_sitting() {
|
||||
if !mob.is_tamed() || mob.is_sitting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -105,3 +105,14 @@ impl Goal for OwnerHurtTargetGoal {
|
||||
Controls::TARGET
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn owner_hurt_target_goal_controls() {
|
||||
let goal = OwnerHurtTargetGoal::new();
|
||||
assert!(goal.controls().get(Controls::TARGET));
|
||||
}
|
||||
}
|
||||
|
||||
125
crates/pumpkin/src/entity/ai/goal/try_find_water.rs
Normal file
125
crates/pumpkin/src/entity/ai/goal/try_find_water.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_data::tag::{self, Taggable};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
use super::{Controls, Goal, GoalFuture};
|
||||
use crate::entity::mob::Mob;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct TryFindWaterGoal;
|
||||
|
||||
impl Default for TryFindWaterGoal {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFindWaterGoal {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn is_water(world: &World, pos: &BlockPos) -> bool {
|
||||
let (_, state_id) = world.get_block_and_state_id(pos);
|
||||
if state_id.to_state().is_waterlogged() {
|
||||
return true;
|
||||
}
|
||||
Fluid::from_state_id(state_id)
|
||||
.is_some_and(|fluid| fluid.has_tag(&tag::Fluid::MINECRAFT_WATER))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_water_range(
|
||||
pos: pumpkin_util::math::vector3::Vector3<f64>,
|
||||
) -> (BlockPos, BlockPos) {
|
||||
let min_x = (pos.x - 2.0).floor() as i32;
|
||||
let min_y = (pos.y - 2.0).floor() as i32;
|
||||
let min_z = (pos.z - 2.0).floor() as i32;
|
||||
let max_x = (pos.x + 2.0).floor() as i32;
|
||||
let max_y = pos.y.floor() as i32;
|
||||
let max_z = (pos.z + 2.0).floor() as i32;
|
||||
|
||||
(
|
||||
BlockPos::new(min_x, min_y, min_z),
|
||||
BlockPos::new(max_x, max_y, max_z),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for TryFindWaterGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let entity = mob.get_entity();
|
||||
if !entity.on_ground.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let world = entity.world.load();
|
||||
let block_pos = entity.block_pos.load();
|
||||
!Self::is_water(&world, &block_pos)
|
||||
})
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = mob.get_entity();
|
||||
let world = entity.world.load();
|
||||
let mob_pos = entity.pos.load();
|
||||
|
||||
let (min_pos, max_pos) = Self::find_water_range(mob_pos);
|
||||
let mut water_pos: Option<BlockPos> = None;
|
||||
|
||||
'outer: for x in min_pos.0.x..=max_pos.0.x {
|
||||
for y in min_pos.0.y..=max_pos.0.y {
|
||||
for z in min_pos.0.z..=max_pos.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
if Self::is_water(&world, &pos) {
|
||||
water_pos = Some(pos);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pos) = water_pos {
|
||||
mob.get_mob_entity()
|
||||
.move_control
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.set_wanted_position(
|
||||
f64::from(pos.0.x),
|
||||
f64::from(pos.0.y),
|
||||
f64::from(pos.0.z),
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
Controls::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
#[test]
|
||||
fn find_water_range_calculation() {
|
||||
let pos = Vector3::new(10.5, 64.0, -5.2);
|
||||
let (min, max) = TryFindWaterGoal::find_water_range(pos);
|
||||
|
||||
assert_eq!(min.0.x, 8);
|
||||
assert_eq!(min.0.y, 62);
|
||||
assert_eq!(min.0.z, -8);
|
||||
|
||||
assert_eq!(max.0.x, 12);
|
||||
assert_eq!(max.0.y, 64);
|
||||
assert_eq!(max.0.z, -4);
|
||||
}
|
||||
}
|
||||
@ -473,4 +473,14 @@ impl Navigator {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
self.is_idle.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_path(&self) -> Option<&Path> {
|
||||
self.current_path.as_ref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_path_mut(&mut self) -> Option<&mut Path> {
|
||||
self.current_path.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
@ -650,6 +650,10 @@ pub trait Mob: EntityBase + Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_iron_golem(&self) -> Option<&crate::entity::passive::iron_golem::IronGolemEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, _nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async {})
|
||||
}
|
||||
@ -833,6 +837,11 @@ pub trait Mob: EntityBase + Send + Sync {
|
||||
.is_some_and(crate::entity::passive::tamable::TamableAnimal::is_in_sitting_pose)
|
||||
}
|
||||
|
||||
fn is_tamed(&self) -> bool {
|
||||
self.as_tamable()
|
||||
.is_some_and(crate::entity::passive::tamable::TamableAnimal::is_tame)
|
||||
}
|
||||
|
||||
fn get_base_experience_reward(&self) -> u32 {
|
||||
self.get_entity().entity_type.experience_reward
|
||||
}
|
||||
|
||||
@ -6,8 +6,8 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal,
|
||||
swim::SwimGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -34,6 +34,7 @@ impl PiglinEntity {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(OpenDoorGoal::new(true)));
|
||||
// Piglins use crossbows or swords, but for now we give them melee
|
||||
goal_selector.add_goal(2, Box::new(MeleeAttackGoal::new(1.0, true)));
|
||||
goal_selector.add_goal(5, Box::new(WanderAroundGoal::new(1.0)));
|
||||
|
||||
@ -6,8 +6,8 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal,
|
||||
swim::SwimGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -34,6 +34,7 @@ impl PiglinBruteEntity {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(OpenDoorGoal::new(true)));
|
||||
goal_selector.add_goal(2, Box::new(MeleeAttackGoal::new(1.0, true)));
|
||||
goal_selector.add_goal(5, Box::new(WanderAroundGoal::new(1.0)));
|
||||
goal_selector.add_goal(
|
||||
|
||||
@ -8,8 +8,8 @@ use crate::entity::{
|
||||
Entity, NbtFuture,
|
||||
ai::goal::{
|
||||
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal,
|
||||
swim::SwimGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{
|
||||
Mob, MobEntity,
|
||||
@ -48,6 +48,7 @@ impl VindicatorEntity {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(OpenDoorGoal::new(true)));
|
||||
goal_selector.add_goal(1, Box::new(ObtainRaidLeaderBannerGoal));
|
||||
goal_selector.add_goal(2, Box::new(HoldGroundAttackGoal::new(10.0)));
|
||||
goal_selector.add_goal(3, Box::new(MeleeAttackGoal::new(1.0, true)));
|
||||
|
||||
@ -2,9 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use crate::entity::mob::zombie::ZombieEntityBase;
|
||||
use crate::entity::{
|
||||
Entity,
|
||||
Entity, NbtFuture,
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
|
||||
pub struct DrownedEntity {
|
||||
entity: Arc<ZombieEntityBase>,
|
||||
@ -32,10 +33,29 @@ impl DrownedEntity {
|
||||
|
||||
mob_arc
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_can_break_doors(entity: Entity, can_break_doors: bool) -> Arc<Self> {
|
||||
let entity = ZombieEntityBase::with_can_break_doors(entity, can_break_doors);
|
||||
let zombie = Self { entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for DrownedEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.entity.mob_entity
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_write_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_read_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use crate::entity::mob::zombie::ZombieEntityBase;
|
||||
use crate::entity::{
|
||||
Entity,
|
||||
Entity, NbtFuture,
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
|
||||
pub struct HuskEntity {
|
||||
entity: Arc<ZombieEntityBase>,
|
||||
@ -16,10 +17,29 @@ impl HuskEntity {
|
||||
let zombie = Self { entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_can_break_doors(entity: Entity, can_break_doors: bool) -> Arc<Self> {
|
||||
let entity = ZombieEntityBase::with_can_break_doors(entity, can_break_doors);
|
||||
let zombie = Self { entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for HuskEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.entity.mob_entity
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_write_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_read_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use super::{Mob, MobEntity};
|
||||
use crate::entity::NbtFuture;
|
||||
use crate::entity::ai::goal::break_door::BreakDoorGoal;
|
||||
use crate::entity::ai::goal::destroy_egg::DestroyEggGoal;
|
||||
use crate::entity::ai::goal::look_around::RandomLookAroundGoal;
|
||||
use crate::entity::ai::goal::revenge::RevengeGoal;
|
||||
@ -7,9 +9,11 @@ use crate::entity::ai::goal::wander_around::WanderAroundGoal;
|
||||
use crate::entity::ai::goal::zombie_attack::ZombieAttackGoal;
|
||||
use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{active_target::ActiveTargetGoal, look_at_entity::LookAtEntityGoal},
|
||||
ai::goal::{Goal, active_target::ActiveTargetGoal, look_at_entity::LookAtEntityGoal},
|
||||
};
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub mod drowned;
|
||||
@ -20,12 +24,20 @@ pub mod zombie_villager;
|
||||
|
||||
pub struct ZombieEntityBase {
|
||||
pub mob_entity: MobEntity,
|
||||
pub can_break_doors: AtomicBool,
|
||||
}
|
||||
|
||||
impl ZombieEntityBase {
|
||||
pub fn new(entity: Entity) -> Arc<Self> {
|
||||
Self::with_can_break_doors(entity, false)
|
||||
}
|
||||
|
||||
pub fn with_can_break_doors(entity: Entity, can_break_doors: bool) -> Arc<Self> {
|
||||
let mob_entity = MobEntity::new(entity);
|
||||
let zombie = Self { mob_entity };
|
||||
let zombie = Self {
|
||||
mob_entity,
|
||||
can_break_doors: AtomicBool::new(can_break_doors),
|
||||
};
|
||||
let mob_arc = Arc::new(zombie);
|
||||
let mob_weak: Weak<dyn Mob> = {
|
||||
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
||||
@ -45,6 +57,9 @@ impl ZombieEntityBase {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
if can_break_doors {
|
||||
goal_selector.add_goal(1, Box::new(BreakDoorGoal::default()));
|
||||
}
|
||||
goal_selector.add_goal(2, ZombieAttackGoal::new(1.0, false));
|
||||
goal_selector.add_goal(4, DestroyEggGoal::new(1.0, 3));
|
||||
goal_selector.add_goal(7, Box::new(WanderAroundGoal::new(1.0)));
|
||||
@ -75,10 +90,56 @@ impl ZombieEntityBase {
|
||||
|
||||
mob_arc
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn can_break_doors(&self) -> bool {
|
||||
self.can_break_doors.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub async fn set_can_break_doors(&self, can_break_doors: bool, mob: &dyn Mob) {
|
||||
if self
|
||||
.can_break_doors
|
||||
.swap(can_break_doors, Ordering::Relaxed)
|
||||
!= can_break_doors
|
||||
{
|
||||
let mut stopped = {
|
||||
let mut goal_selector = self
|
||||
.mob_entity
|
||||
.goals_selector
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if can_break_doors {
|
||||
goal_selector.add_goal(1, Box::new(BreakDoorGoal::default()));
|
||||
Vec::new()
|
||||
} else {
|
||||
goal_selector.remove_goal_sync::<BreakDoorGoal>()
|
||||
}
|
||||
};
|
||||
for goal in &mut stopped {
|
||||
goal.stop(mob).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for ZombieEntityBase {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.mob_entity
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if self.can_break_doors() {
|
||||
nbt.put_bool("CanBreakDoors", true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(can_break_doors) = nbt.get_bool("CanBreakDoors") {
|
||||
self.set_can_break_doors(can_break_doors, self).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::mob::zombie::ZombieEntityBase;
|
||||
use crate::entity::mob::{Mob, MobEntity};
|
||||
use crate::entity::{Entity, NbtFuture};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ZombieEntity {
|
||||
@ -13,10 +14,29 @@ impl ZombieEntity {
|
||||
let zombie = Self { entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_can_break_doors(entity: Entity, can_break_doors: bool) -> Arc<Self> {
|
||||
let entity = ZombieEntityBase::with_can_break_doors(entity, can_break_doors);
|
||||
let zombie = Self { entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for ZombieEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.entity.mob_entity
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_write_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.mob_read_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::mob::zombie::ZombieEntityBase;
|
||||
use crate::entity::mob::{Mob, MobEntity};
|
||||
use crate::entity::{Entity, NbtFuture};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ZombieVillagerEntity {
|
||||
@ -13,10 +14,29 @@ impl ZombieVillagerEntity {
|
||||
let zombie = Self { mob_entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_can_break_doors(entity: Entity, can_break_doors: bool) -> Arc<Self> {
|
||||
let mob_entity = ZombieEntityBase::with_can_break_doors(entity, can_break_doors);
|
||||
let zombie = Self { mob_entity };
|
||||
Arc::new(zombie)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for ZombieVillagerEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.mob_entity.mob_entity
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.mob_entity.mob_write_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.mob_entity.mob_read_nbt(nbt).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
try_find_water::TryFindWaterGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -35,6 +35,7 @@ impl AxolotlEntity {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(TryFindWaterGoal));
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(WanderAroundGoal::new(1.0)));
|
||||
goal_selector.add_goal(
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
try_find_water::TryFindWaterGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -35,6 +35,7 @@ impl DolphinEntity {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(TryFindWaterGoal));
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(WanderAroundGoal::new(1.0)));
|
||||
goal_selector.add_goal(
|
||||
|
||||
@ -3,7 +3,7 @@ use std::sync::{
|
||||
atomic::{AtomicBool, AtomicI32, Ordering},
|
||||
};
|
||||
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::entity::{EntityStatus, EntityType};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
@ -15,8 +15,8 @@ use crate::entity::{
|
||||
Entity, EntityBase, EntityBaseFuture, NbtFuture,
|
||||
ai::goal::{
|
||||
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, revenge::RevengeGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal,
|
||||
offer_flower::OfferFlowerGoal, revenge::RevengeGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
player::Player,
|
||||
@ -60,6 +60,7 @@ impl IronGolemEntity {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(1, Box::new(MeleeAttackGoal::new(1.0, true)));
|
||||
goal_selector.add_goal(5, Box::new(OfferFlowerGoal::new()));
|
||||
goal_selector.add_goal(6, Box::new(WanderAroundGoal::new(0.6)));
|
||||
goal_selector.add_goal(
|
||||
7,
|
||||
@ -98,9 +99,29 @@ impl IronGolemEntity {
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn offer_flower(&self, offer: bool) {
|
||||
let entity = self.get_entity();
|
||||
let world = entity.world.load();
|
||||
if offer {
|
||||
self.offer_flower_tick.store(400, Ordering::Relaxed);
|
||||
world.send_entity_status(entity, EntityStatus::OfferFlower, None);
|
||||
} else {
|
||||
self.offer_flower_tick.store(0, Ordering::Relaxed);
|
||||
world.send_entity_status(entity, EntityStatus::StopOfferFlower, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_offer_flower_tick(&self) -> i32 {
|
||||
self.offer_flower_tick.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for IronGolemEntity {
|
||||
fn as_iron_golem(&self) -> Option<&IronGolemEntity> {
|
||||
Some(self)
|
||||
}
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
nbt.put_bool("PlayerCreated", self.is_player_created());
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
try_find_water::TryFindWaterGoal, wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -32,6 +32,7 @@ impl TadpoleEntity {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(TryFindWaterGoal));
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(1, Box::new(WanderAroundGoal::new(1.0)));
|
||||
goal_selector.add_goal(
|
||||
|
||||
@ -6,7 +6,8 @@ use crate::entity::{
|
||||
Entity,
|
||||
ai::goal::{
|
||||
breed::BreedGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
|
||||
swim::SwimGoal, tempt::TemptGoal, wander_around::WanderAroundGoal,
|
||||
swim::SwimGoal, tempt::TemptGoal, try_find_water::TryFindWaterGoal,
|
||||
wander_around::WanderAroundGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
@ -34,6 +35,7 @@ impl TurtleEntity {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(TryFindWaterGoal));
|
||||
goal_selector.add_goal(1, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(2, BreedGoal::new(1.0));
|
||||
goal_selector.add_goal(3, Box::new(TemptGoal::new(1.1, TEMPT_ITEMS)));
|
||||
|
||||
@ -39,7 +39,7 @@ use crate::entity::{
|
||||
ai::{
|
||||
goal::{
|
||||
avoid_entity::AvoidEntityGoal, look_around::RandomLookAroundGoal,
|
||||
look_at_entity::LookAtEntityGoal, swim::SwimGoal,
|
||||
look_at_entity::LookAtEntityGoal, open_door::OpenDoorGoal, swim::SwimGoal,
|
||||
trade_with_player::TradeWithPlayerGoal, wander_around::WanderAroundGoal,
|
||||
work_at_job_site::WorkAtJobSiteGoal,
|
||||
},
|
||||
@ -371,6 +371,7 @@ impl VillagerEntity {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
goal_selector.add_goal(0, Box::new(SwimGoal::default()));
|
||||
goal_selector.add_goal(0, Box::new(OpenDoorGoal::new(true)));
|
||||
// Villagers avoid threats
|
||||
goal_selector.add_goal(
|
||||
1,
|
||||
|
||||
@ -175,6 +175,54 @@ impl Mob for WolfEntity {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn can_attack_with_owner(&self, target: &dyn EntityBase, owner: &dyn EntityBase) -> bool {
|
||||
let target_entity = target.get_entity();
|
||||
let target_type = target_entity.entity_type;
|
||||
if *target_type == EntityType::CREEPER
|
||||
|| *target_type == EntityType::GHAST
|
||||
|| *target_type == EntityType::ARMOR_STAND
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if *target_type == EntityType::WOLF {
|
||||
if let Some(target_mob) = target.get_mob()
|
||||
&& let Some(tamable) = target_mob.as_tamable()
|
||||
&& tamable.is_tame()
|
||||
&& let Some(target_owner) = tamable.get_owner()
|
||||
&& let Some(owner_player) = owner.get_player()
|
||||
&& target_owner == owner_player.gameprofile.id
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if *target_type == EntityType::PLAYER {
|
||||
if let Some(owner_player) = owner.get_player()
|
||||
&& let Some(target_player) = target.get_player()
|
||||
{
|
||||
if owner_player.gameprofile.id == target_player.gameprofile.id {
|
||||
return false;
|
||||
}
|
||||
let world = target_player.world();
|
||||
if !world.level_info.load().game_rules.pvp {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(target_mob) = target.get_mob()
|
||||
&& let Some(tamable) = target_mob.as_tamable()
|
||||
&& tamable.is_tame()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async {
|
||||
let variant_str = match self.variant.load(Ordering::Relaxed) {
|
||||
|
||||
@ -1042,7 +1042,7 @@ impl JavaClient {
|
||||
&mut payload,
|
||||
&version,
|
||||
)?;
|
||||
let mut event = crate::plugin::api::events::player::custom_click_action::CustomClickActionEvent::new(
|
||||
let mut event = crate::plugin::api::events::dialog::dialog_click_action::DialogClickActionEvent::new(
|
||||
player.clone(),
|
||||
packet.action_id.to_string(),
|
||||
packet.payload.map(Bytes::copy_from_slice),
|
||||
|
||||
30
crates/pumpkin/src/plugin/api/events/dialog/dialog_clear.rs
Normal file
30
crates/pumpkin/src/plugin/api/events/dialog/dialog_clear.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use pumpkin_macros::{Event, cancellable};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
|
||||
use super::super::player::PlayerEvent;
|
||||
|
||||
/// An event that occurs when a dialog is cleared for a player.
|
||||
#[cancellable]
|
||||
#[derive(Event, Clone)]
|
||||
pub struct DialogClearEvent {
|
||||
/// The player whose dialog is being cleared.
|
||||
pub player: Arc<Player>,
|
||||
}
|
||||
|
||||
impl DialogClearEvent {
|
||||
#[must_use]
|
||||
pub const fn new(player: Arc<Player>) -> Self {
|
||||
Self {
|
||||
player,
|
||||
cancelled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerEvent for DialogClearEvent {
|
||||
fn get_player(&self) -> &Arc<Player> {
|
||||
&self.player
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,15 @@
|
||||
use bytes::Bytes;
|
||||
use pumpkin_macros::Event;
|
||||
use pumpkin_macros::{Event, cancellable};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
|
||||
use super::PlayerEvent;
|
||||
use super::super::player::PlayerEvent;
|
||||
|
||||
/// An event that occurs when a player clicks a custom dialog button.
|
||||
#[cancellable]
|
||||
#[derive(Event, Clone)]
|
||||
pub struct CustomClickActionEvent {
|
||||
pub struct DialogClickActionEvent {
|
||||
/// The player who clicked the button.
|
||||
pub player: Arc<Player>,
|
||||
/// The unique identifier for the action.
|
||||
@ -17,18 +18,19 @@ pub struct CustomClickActionEvent {
|
||||
pub payload: Option<Bytes>,
|
||||
}
|
||||
|
||||
impl CustomClickActionEvent {
|
||||
impl DialogClickActionEvent {
|
||||
#[must_use]
|
||||
pub const fn new(player: Arc<Player>, id: String, payload: Option<Bytes>) -> Self {
|
||||
Self {
|
||||
player,
|
||||
id,
|
||||
payload,
|
||||
cancelled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerEvent for CustomClickActionEvent {
|
||||
impl PlayerEvent for DialogClickActionEvent {
|
||||
fn get_player(&self) -> &Arc<Player> {
|
||||
&self.player
|
||||
}
|
||||
34
crates/pumpkin/src/plugin/api/events/dialog/dialog_show.rs
Normal file
34
crates/pumpkin/src/plugin/api/events/dialog/dialog_show.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use pumpkin_macros::{Event, cancellable};
|
||||
use pumpkin_protocol::java::client::dialog::Dialog;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
|
||||
use super::super::player::PlayerEvent;
|
||||
|
||||
/// An event that occurs when a dialog is shown to a player.
|
||||
#[cancellable]
|
||||
#[derive(Event, Clone)]
|
||||
pub struct DialogShowEvent {
|
||||
/// The player receiving the dialog.
|
||||
pub player: Arc<Player>,
|
||||
/// The dialog being shown.
|
||||
pub dialog: Dialog,
|
||||
}
|
||||
|
||||
impl DialogShowEvent {
|
||||
#[must_use]
|
||||
pub const fn new(player: Arc<Player>, dialog: Dialog) -> Self {
|
||||
Self {
|
||||
player,
|
||||
dialog,
|
||||
cancelled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerEvent for DialogShowEvent {
|
||||
fn get_player(&self) -> &Arc<Player> {
|
||||
&self.player
|
||||
}
|
||||
}
|
||||
7
crates/pumpkin/src/plugin/api/events/dialog/mod.rs
Normal file
7
crates/pumpkin/src/plugin/api/events/dialog/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod dialog_clear;
|
||||
pub mod dialog_click_action;
|
||||
pub mod dialog_show;
|
||||
|
||||
pub use dialog_clear::*;
|
||||
pub use dialog_click_action::*;
|
||||
pub use dialog_show::*;
|
||||
@ -2,6 +2,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod block;
|
||||
pub mod dialog;
|
||||
pub mod enchantment;
|
||||
pub mod entity;
|
||||
pub mod hanging;
|
||||
|
||||
@ -2,7 +2,6 @@ pub mod async_player_chat;
|
||||
pub mod async_player_pre_login;
|
||||
pub mod bedrock_form_response;
|
||||
pub mod changed_main_hand;
|
||||
pub mod custom_click_action;
|
||||
pub mod egg_throw;
|
||||
pub mod exp_change;
|
||||
pub mod fish;
|
||||
@ -79,7 +78,6 @@ pub use async_player_chat::*;
|
||||
pub use async_player_pre_login::*;
|
||||
pub use bedrock_form_response::*;
|
||||
pub use changed_main_hand::*;
|
||||
pub use custom_click_action::*;
|
||||
pub use egg_throw::*;
|
||||
pub use exp_change::*;
|
||||
pub use fish::*;
|
||||
|
||||
@ -48,8 +48,8 @@ fn register_player_event(
|
||||
) {
|
||||
use crate::plugin::player::{
|
||||
bedrock_form_response::BedrockFormResponseEvent,
|
||||
changed_main_hand::PlayerChangedMainHandEvent, custom_click_action::CustomClickActionEvent,
|
||||
egg_throw::PlayerEggThrowEvent, exp_change::PlayerExpChangeEvent, fish::PlayerFishEvent,
|
||||
changed_main_hand::PlayerChangedMainHandEvent, egg_throw::PlayerEggThrowEvent,
|
||||
exp_change::PlayerExpChangeEvent, fish::PlayerFishEvent,
|
||||
inventory_close::InventoryCloseEvent, inventory_interact::InventoryClickEvent,
|
||||
item_held::PlayerItemHeldEvent, player_change_world::PlayerChangeWorldEvent,
|
||||
player_chat::PlayerChatEvent, player_command_send::PlayerCommandSendEvent,
|
||||
@ -155,9 +155,6 @@ fn register_player_event(
|
||||
EventType::BedrockFormResponseEvent => {
|
||||
register_typed_event::<BedrockFormResponseEvent>(resource, handler, priority, blocking);
|
||||
}
|
||||
EventType::CustomClickActionEvent => {
|
||||
register_typed_event::<CustomClickActionEvent>(resource, handler, priority, blocking);
|
||||
}
|
||||
EventType::PlayerItemConsumeEvent => {
|
||||
register_typed_event::<
|
||||
crate::plugin::api::events::player::player_item_consume::PlayerItemConsumeEvent,
|
||||
@ -1368,6 +1365,33 @@ fn register_raid_event(
|
||||
}
|
||||
}
|
||||
|
||||
fn register_dialog_event(
|
||||
resource: &ContextResource,
|
||||
handler: &Arc<WasmPluginEventHandler>,
|
||||
priority: crate::plugin::EventPriority,
|
||||
blocking: bool,
|
||||
event_type: EventType,
|
||||
) {
|
||||
use crate::plugin::api::events::dialog::{
|
||||
DialogClearEvent, DialogClickActionEvent, DialogShowEvent,
|
||||
};
|
||||
|
||||
match event_type {
|
||||
EventType::DialogClickActionEvent => {
|
||||
register_typed_event::<DialogClickActionEvent>(resource, handler, priority, blocking);
|
||||
}
|
||||
EventType::DialogShowEvent => {
|
||||
register_typed_event::<DialogShowEvent>(resource, handler, priority, blocking);
|
||||
}
|
||||
EventType::DialogClearEvent => {
|
||||
register_typed_event::<DialogClearEvent>(resource, handler, priority, blocking);
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("non-dialog event should not be routed to register_dialog_event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_server_event(
|
||||
resource: &ContextResource,
|
||||
handler: &Arc<WasmPluginEventHandler>,
|
||||
@ -1721,6 +1745,11 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
|
||||
| EventType::RaidTriggerEvent) => {
|
||||
register_raid_event(resource, &handler, priority, blocking, event_type);
|
||||
}
|
||||
event_type @ (EventType::DialogClickActionEvent
|
||||
| EventType::DialogShowEvent
|
||||
| EventType::DialogClearEvent) => {
|
||||
register_dialog_event(resource, &handler, priority, blocking, event_type);
|
||||
}
|
||||
event_type => {
|
||||
register_player_event(resource, &handler, priority, blocking, event_type);
|
||||
}
|
||||
|
||||
@ -23,7 +23,9 @@ use crate::plugin::loader::wasm::wasm_host::{
|
||||
uuid::Uuid,
|
||||
world::{
|
||||
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity,
|
||||
EquipmentSlot as WitEquipmentSlot, HostEntity, RaycastResult as WitRaycastResult,
|
||||
EquipmentSlot as WitEquipmentSlot, HostEntity,
|
||||
RayTraceBlockResult as WitRayTraceBlockResult,
|
||||
RayTraceEntityResult as WitRayTraceEntityResult, RaycastResult as WitRaycastResult,
|
||||
World,
|
||||
},
|
||||
},
|
||||
@ -1281,7 +1283,7 @@ impl HostEntity for PluginHostState {
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
max_distance: f64,
|
||||
_fluid_handling: bool,
|
||||
fluid_handling: bool,
|
||||
) -> wasmtime::Result<Option<WitRaycastResult>> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let start = entity.get_eye_pos();
|
||||
@ -1289,22 +1291,9 @@ impl HostEntity for PluginHostState {
|
||||
let end = start + direction * max_distance;
|
||||
let world = entity.get_entity().world.load_full();
|
||||
|
||||
let hit = world
|
||||
.raycast(
|
||||
start,
|
||||
end,
|
||||
|pos: &pumpkin_util::math::position::BlockPos, w: &Arc<crate::world::World>| {
|
||||
let pos = *pos;
|
||||
let world = w.clone();
|
||||
async move {
|
||||
let block = world.get_block_state(&pos);
|
||||
!block.is_air()
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let hit = world.ray_trace_block(start, end, fluid_handling);
|
||||
|
||||
Ok(hit.map(|(pos, face)| WitRaycastResult {
|
||||
Ok(hit.map(|(pos, face, _)| WitRaycastResult {
|
||||
pos: WitBlockPos {
|
||||
x: pos.0.x,
|
||||
y: pos.0.y,
|
||||
@ -1314,6 +1303,69 @@ impl HostEntity for PluginHostState {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ray_trace_block(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
max_distance: f64,
|
||||
include_fluids: bool,
|
||||
) -> wasmtime::Result<Option<WitRayTraceBlockResult>> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let start = entity.get_eye_pos();
|
||||
let direction = entity.get_looking_vector();
|
||||
let end = start + direction * max_distance;
|
||||
let world = entity.get_entity().world.load_full();
|
||||
|
||||
let hit = world.ray_trace_block(start, end, include_fluids);
|
||||
|
||||
Ok(hit.map(|(pos, face, hit_pos)| WitRayTraceBlockResult {
|
||||
pos: WitBlockPos {
|
||||
x: pos.0.x,
|
||||
y: pos.0.y,
|
||||
z: pos.0.z,
|
||||
},
|
||||
face: to_wasm_block_direction(face),
|
||||
hit_pos: to_wasm_position(hit_pos),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ray_trace_entity(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
max_distance: f64,
|
||||
) -> wasmtime::Result<Option<WitRayTraceEntityResult>> {
|
||||
let entity_base = entity_from_resource(self, &entity)?;
|
||||
let start = entity_base.get_eye_pos();
|
||||
let direction = entity_base.get_looking_vector();
|
||||
let end = start + direction * max_distance;
|
||||
let world = entity_base.get_entity().world.load_full();
|
||||
let self_id = entity_base.get_entity().entity_id;
|
||||
|
||||
let hits = world.ray_trace_entities(start, end);
|
||||
for (hit_entity, hit_pos, distance) in hits {
|
||||
if hit_entity.get_entity().entity_id != self_id {
|
||||
let entity_res = self
|
||||
.add_entity(hit_entity)
|
||||
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?;
|
||||
return Ok(Some(WitRayTraceEntityResult {
|
||||
entity: entity_res,
|
||||
hit_pos: to_wasm_position(hit_pos),
|
||||
distance,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn get_target_entity(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
max_distance: f64,
|
||||
) -> wasmtime::Result<Option<Resource<Entity>>> {
|
||||
let res = self.ray_trace_entity(entity, max_distance).await?;
|
||||
Ok(res.map(|r| r.entity))
|
||||
}
|
||||
|
||||
async fn set_custom_data(
|
||||
&mut self,
|
||||
this: Resource<Entity>,
|
||||
|
||||
@ -164,9 +164,53 @@ pub fn cleanup_event(event: &Event, state: &mut PluginHostState) {
|
||||
Event::BedrockFormResponseEvent(data) => {
|
||||
cleanup_player(state, &data.player);
|
||||
}
|
||||
Event::CustomClickActionEvent(data) => {
|
||||
Event::DialogClickActionEvent(data) => {
|
||||
cleanup_player(state, &data.player);
|
||||
}
|
||||
Event::DialogClearEvent(data) => {
|
||||
cleanup_player(state, &data.player);
|
||||
}
|
||||
Event::DialogShowEvent(data) => {
|
||||
cleanup_player(state, &data.player);
|
||||
cleanup_text_component(state, &data.dialog.title);
|
||||
for body in &data.dialog.body {
|
||||
match body {
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogBody::PlainMessage(c) => cleanup_text_component(state, c),
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogBody::Item(item) => cleanup_item_stack(state, item),
|
||||
}
|
||||
}
|
||||
for input in &data.dialog.inputs {
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogInput;
|
||||
match input {
|
||||
DialogInput::Bool(b) => cleanup_text_component(state, &b.label),
|
||||
DialogInput::Text(t) => {
|
||||
cleanup_text_component(state, &t.label);
|
||||
cleanup_text_component(state, &t.placeholder);
|
||||
}
|
||||
DialogInput::NumberRange(n) => cleanup_text_component(state, &n.label),
|
||||
DialogInput::SingleOption(s) => {
|
||||
cleanup_text_component(state, &s.label);
|
||||
for opt in &s.options {
|
||||
cleanup_text_component(state, opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for button in &data.dialog.buttons {
|
||||
cleanup_text_component(state, &button.text);
|
||||
if let Some(tooltip) = &button.tooltip {
|
||||
cleanup_text_component(state, tooltip);
|
||||
}
|
||||
}
|
||||
for link in &data.dialog.links {
|
||||
if let crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::LinkLabel::Custom(c) = &link.label {
|
||||
cleanup_text_component(state, c);
|
||||
}
|
||||
}
|
||||
if let Some(ext_title) = &data.dialog.external_title {
|
||||
cleanup_text_component(state, ext_title);
|
||||
}
|
||||
}
|
||||
Event::ServerCommandEvent(_) => {}
|
||||
Event::ServerListPingEvent(data) => {
|
||||
cleanup_text_component(state, &data.motd);
|
||||
|
||||
@ -0,0 +1,428 @@
|
||||
use bytes::Bytes;
|
||||
use pumpkin_protocol::java::client::dialog::{
|
||||
ActionButton as ProtocolActionButton, Dialog as ProtocolDialog, DialogAction,
|
||||
DialogBody as ProtocolDialogBody, DialogInput as ProtocolDialogInput, DialogLink,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::plugin::api::events::dialog::{
|
||||
DialogClearEvent, DialogClickActionEvent, DialogShowEvent,
|
||||
};
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
state::PluginHostState,
|
||||
wit::v0_1::{
|
||||
events::{ToFromWasmEvent, consume_player},
|
||||
player::text_component_from_resource,
|
||||
pumpkin::plugin::{
|
||||
event::{DialogClearEventData, DialogClickActionEventData, DialogShowEventData, Event},
|
||||
java_dialogs::{
|
||||
Action, ActionButton, AfterAction, CustomClickAction, Dialog, DialogBody,
|
||||
DialogInput, DialogInputBool, DialogInputNumberRange, DialogInputSingleOption,
|
||||
DialogInputText, DialogType, Link, LinkLabel, LinkType,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn protocol_dialog_from_wasm(
|
||||
state: &PluginHostState,
|
||||
dialog: &Dialog,
|
||||
) -> ProtocolDialog {
|
||||
let title = text_component_from_resource(state, &dialog.title);
|
||||
|
||||
let body: Vec<_> = dialog
|
||||
.body
|
||||
.iter()
|
||||
.map(|b| match b {
|
||||
DialogBody::PlainMessage(c) => ProtocolDialogBody::PlainMessage {
|
||||
contents: text_component_from_resource(state, c),
|
||||
},
|
||||
DialogBody::Item(_i) => ProtocolDialogBody::Item { item: 0 },
|
||||
})
|
||||
.collect();
|
||||
|
||||
let inputs: Vec<_> = dialog
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| match i {
|
||||
DialogInput::Bool(b) => ProtocolDialogInput::Boolean {
|
||||
label: text_component_from_resource(state, &b.label),
|
||||
default_value: b.default_value,
|
||||
},
|
||||
DialogInput::Text(t) => ProtocolDialogInput::Text {
|
||||
label: text_component_from_resource(state, &t.label),
|
||||
placeholder: text_component_from_resource(state, &t.placeholder),
|
||||
default_value: t.default_value.clone(),
|
||||
},
|
||||
DialogInput::NumberRange(n) => ProtocolDialogInput::NumberRange {
|
||||
label: text_component_from_resource(state, &n.label),
|
||||
min: n.min_value,
|
||||
max: n.max_value,
|
||||
initial: n.initial_value,
|
||||
step: n.step,
|
||||
label_format: n.label_format.clone(),
|
||||
},
|
||||
DialogInput::SingleOption(s) => ProtocolDialogInput::SingleOption {
|
||||
label: text_component_from_resource(state, &s.label),
|
||||
options: s
|
||||
.options
|
||||
.iter()
|
||||
.map(|o| text_component_from_resource(state, o))
|
||||
.collect(),
|
||||
initial_index: s.initial_index,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let buttons: Vec<_> = dialog
|
||||
.buttons
|
||||
.iter()
|
||||
.map(|b| ProtocolActionButton {
|
||||
text: text_component_from_resource(state, &b.text),
|
||||
tooltip: b
|
||||
.tooltip
|
||||
.as_ref()
|
||||
.map(|t| text_component_from_resource(state, t)),
|
||||
width: b.width,
|
||||
action: match &b.action {
|
||||
Action::OpenUrl(u) => DialogAction::OpenUrl { url: u.clone() },
|
||||
Action::CustomClick(c) => DialogAction::Custom {
|
||||
id: c.id.clone(),
|
||||
payload: c.payload.clone(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let links: Vec<_> = dialog
|
||||
.links
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let label = match &l.label {
|
||||
LinkLabel::BuiltIn(t) => {
|
||||
let link_type = match t {
|
||||
LinkType::BugReport => pumpkin_protocol::LinkType::BugReport,
|
||||
LinkType::CommunityGuidelines => {
|
||||
pumpkin_protocol::LinkType::CommunityGuidelines
|
||||
}
|
||||
LinkType::Support => pumpkin_protocol::LinkType::Support,
|
||||
LinkType::Status => pumpkin_protocol::LinkType::Status,
|
||||
LinkType::Feedback => pumpkin_protocol::LinkType::Feedback,
|
||||
LinkType::Community => pumpkin_protocol::LinkType::Community,
|
||||
LinkType::Website => pumpkin_protocol::LinkType::Website,
|
||||
LinkType::Forums => pumpkin_protocol::LinkType::Forums,
|
||||
LinkType::News => pumpkin_protocol::LinkType::News,
|
||||
LinkType::Announcements => pumpkin_protocol::LinkType::Announcements,
|
||||
};
|
||||
pumpkin_protocol::Label::BuiltIn(link_type)
|
||||
}
|
||||
LinkLabel::Custom(c) => pumpkin_protocol::Label::TextComponent(Box::new(
|
||||
text_component_from_resource(state, c),
|
||||
)),
|
||||
};
|
||||
DialogLink {
|
||||
label,
|
||||
url: l.url.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
ProtocolDialog {
|
||||
r#type: match dialog.type_ {
|
||||
DialogType::Notice => "minecraft:notice".to_string(),
|
||||
DialogType::Confirmation => "minecraft:confirmation".to_string(),
|
||||
DialogType::MultiAction => "minecraft:multi_action".to_string(),
|
||||
DialogType::DialogList => "minecraft:dialog_list".to_string(),
|
||||
DialogType::ServerLinks => "minecraft:server_links".to_string(),
|
||||
},
|
||||
title,
|
||||
body,
|
||||
inputs,
|
||||
buttons,
|
||||
links,
|
||||
exit_action: None,
|
||||
after_action: dialog.after_action.map(|a| match a {
|
||||
AfterAction::Peek => "peek".to_string(),
|
||||
AfterAction::Pop => "pop".to_string(),
|
||||
}),
|
||||
can_close_with_escape: dialog.can_close_with_escape,
|
||||
external_title: dialog
|
||||
.external_title
|
||||
.as_ref()
|
||||
.map(|t| text_component_from_resource(state, t)),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn protocol_dialog_to_wasm(
|
||||
state: &mut PluginHostState,
|
||||
dialog: &ProtocolDialog,
|
||||
) -> Dialog {
|
||||
let title = state
|
||||
.add_text_component(dialog.title.clone())
|
||||
.expect("failed to add text component");
|
||||
|
||||
let type_ = match dialog.r#type.as_str() {
|
||||
"minecraft:confirmation" => DialogType::Confirmation,
|
||||
"minecraft:multi_action" => DialogType::MultiAction,
|
||||
"minecraft:dialog_list" => DialogType::DialogList,
|
||||
"minecraft:server_links" => DialogType::ServerLinks,
|
||||
_ => DialogType::Notice,
|
||||
};
|
||||
|
||||
let body = dialog
|
||||
.body
|
||||
.iter()
|
||||
.map(|b| match b {
|
||||
ProtocolDialogBody::PlainMessage { contents } => {
|
||||
let comp = state
|
||||
.add_text_component(contents.clone())
|
||||
.expect("failed to add text component");
|
||||
DialogBody::PlainMessage(comp)
|
||||
}
|
||||
ProtocolDialogBody::Item { item: _ } => {
|
||||
let item_res = state
|
||||
.add_item_stack(Arc::new(Mutex::new(
|
||||
pumpkin_data::item_stack::ItemStack::new(0, &pumpkin_data::item::Item::AIR),
|
||||
)))
|
||||
.expect("failed to add item stack resource");
|
||||
DialogBody::Item(item_res)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let inputs = dialog
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| match i {
|
||||
ProtocolDialogInput::Boolean {
|
||||
label,
|
||||
default_value,
|
||||
} => {
|
||||
let lbl = state
|
||||
.add_text_component(label.clone())
|
||||
.expect("failed to add text component");
|
||||
DialogInput::Bool(DialogInputBool {
|
||||
label: lbl,
|
||||
default_value: *default_value,
|
||||
})
|
||||
}
|
||||
ProtocolDialogInput::Text {
|
||||
label,
|
||||
placeholder,
|
||||
default_value,
|
||||
} => {
|
||||
let lbl = state
|
||||
.add_text_component(label.clone())
|
||||
.expect("failed to add text component");
|
||||
let ph = state
|
||||
.add_text_component(placeholder.clone())
|
||||
.expect("failed to add text component");
|
||||
DialogInput::Text(DialogInputText {
|
||||
label: lbl,
|
||||
placeholder: ph,
|
||||
default_value: default_value.clone(),
|
||||
})
|
||||
}
|
||||
ProtocolDialogInput::NumberRange {
|
||||
label,
|
||||
min,
|
||||
max,
|
||||
initial,
|
||||
step,
|
||||
label_format,
|
||||
} => {
|
||||
let lbl = state
|
||||
.add_text_component(label.clone())
|
||||
.expect("failed to add text component");
|
||||
DialogInput::NumberRange(DialogInputNumberRange {
|
||||
label: lbl,
|
||||
min_value: *min,
|
||||
max_value: *max,
|
||||
initial_value: *initial,
|
||||
step: *step,
|
||||
label_format: label_format.clone(),
|
||||
})
|
||||
}
|
||||
ProtocolDialogInput::SingleOption {
|
||||
label,
|
||||
options,
|
||||
initial_index,
|
||||
} => {
|
||||
let lbl = state
|
||||
.add_text_component(label.clone())
|
||||
.expect("failed to add text component");
|
||||
let opts = options
|
||||
.iter()
|
||||
.map(|o| {
|
||||
state
|
||||
.add_text_component(o.clone())
|
||||
.expect("failed to add text component")
|
||||
})
|
||||
.collect();
|
||||
DialogInput::SingleOption(DialogInputSingleOption {
|
||||
label: lbl,
|
||||
options: opts,
|
||||
initial_index: *initial_index,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let buttons = dialog
|
||||
.buttons
|
||||
.iter()
|
||||
.map(|b| {
|
||||
let text = state
|
||||
.add_text_component(b.text.clone())
|
||||
.expect("failed to add text component");
|
||||
let tooltip = b.tooltip.as_ref().map(|t| {
|
||||
state
|
||||
.add_text_component(t.clone())
|
||||
.expect("failed to add text component")
|
||||
});
|
||||
let action = match &b.action {
|
||||
DialogAction::OpenUrl { url } => Action::OpenUrl(url.clone()),
|
||||
DialogAction::Custom { id, payload } => Action::CustomClick(CustomClickAction {
|
||||
id: id.clone(),
|
||||
payload: payload.clone(),
|
||||
}),
|
||||
};
|
||||
ActionButton {
|
||||
text,
|
||||
tooltip,
|
||||
width: b.width,
|
||||
action,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let links = dialog
|
||||
.links
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let label = match &l.label {
|
||||
pumpkin_protocol::Label::BuiltIn(t) => LinkLabel::BuiltIn(match t {
|
||||
pumpkin_protocol::LinkType::BugReport => LinkType::BugReport,
|
||||
pumpkin_protocol::LinkType::CommunityGuidelines => {
|
||||
LinkType::CommunityGuidelines
|
||||
}
|
||||
pumpkin_protocol::LinkType::Support => LinkType::Support,
|
||||
pumpkin_protocol::LinkType::Status => LinkType::Status,
|
||||
pumpkin_protocol::LinkType::Feedback => LinkType::Feedback,
|
||||
pumpkin_protocol::LinkType::Community => LinkType::Community,
|
||||
pumpkin_protocol::LinkType::Website => LinkType::Website,
|
||||
pumpkin_protocol::LinkType::Forums => LinkType::Forums,
|
||||
pumpkin_protocol::LinkType::News => LinkType::News,
|
||||
pumpkin_protocol::LinkType::Announcements => LinkType::Announcements,
|
||||
}),
|
||||
pumpkin_protocol::Label::TextComponent(c) => {
|
||||
let comp = state
|
||||
.add_text_component((**c).clone())
|
||||
.expect("failed to add text component");
|
||||
LinkLabel::Custom(comp)
|
||||
}
|
||||
};
|
||||
Link {
|
||||
label,
|
||||
url: l.url.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let external_title = dialog.external_title.as_ref().map(|t| {
|
||||
state
|
||||
.add_text_component(t.clone())
|
||||
.expect("failed to add text component")
|
||||
});
|
||||
|
||||
Dialog {
|
||||
title,
|
||||
type_,
|
||||
body,
|
||||
inputs,
|
||||
buttons,
|
||||
links,
|
||||
after_action: dialog.after_action.as_deref().map(|a| match a {
|
||||
"peek" => AfterAction::Peek,
|
||||
_ => AfterAction::Pop,
|
||||
}),
|
||||
can_close_with_escape: dialog.can_close_with_escape,
|
||||
external_title,
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for DialogClickActionEvent {
|
||||
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
|
||||
Event::DialogClickActionEvent(DialogClickActionEventData {
|
||||
player: state
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource"),
|
||||
id: self.id.clone(),
|
||||
payload: self.payload.as_ref().map(|p| p.to_vec()),
|
||||
cancelled: self.cancelled,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
|
||||
match event {
|
||||
Event::DialogClickActionEvent(data) => Self {
|
||||
player: consume_player(state, &data.player),
|
||||
id: data.id,
|
||||
payload: data.payload.map(Bytes::from),
|
||||
cancelled: data.cancelled,
|
||||
},
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for DialogClearEvent {
|
||||
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
|
||||
Event::DialogClearEvent(DialogClearEventData {
|
||||
player: state
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource"),
|
||||
cancelled: self.cancelled,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
|
||||
match event {
|
||||
Event::DialogClearEvent(data) => Self {
|
||||
player: consume_player(state, &data.player),
|
||||
cancelled: data.cancelled,
|
||||
},
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for DialogShowEvent {
|
||||
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
|
||||
let dialog = protocol_dialog_to_wasm(state, &self.dialog);
|
||||
Event::DialogShowEvent(DialogShowEventData {
|
||||
player: state
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource"),
|
||||
dialog,
|
||||
cancelled: self.cancelled,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
|
||||
match event {
|
||||
Event::DialogShowEvent(data) => {
|
||||
let dialog = protocol_dialog_from_wasm(state, &data.dialog);
|
||||
Self {
|
||||
player: consume_player(state, &data.player),
|
||||
dialog,
|
||||
cancelled: data.cancelled,
|
||||
}
|
||||
}
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,7 @@ use crate::{
|
||||
|
||||
pub mod block;
|
||||
pub mod cleanup;
|
||||
pub mod dialog;
|
||||
pub mod enchantment;
|
||||
pub mod entity;
|
||||
pub mod hanging;
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::plugin::api::events::player::custom_click_action::CustomClickActionEvent;
|
||||
use crate::plugin::{
|
||||
loader::wasm::wasm_host::{
|
||||
state::PluginHostState,
|
||||
@ -17,12 +16,11 @@ use crate::plugin::{
|
||||
gui::{from_wit_screen, to_wit_screen},
|
||||
pumpkin::plugin::event::{
|
||||
AsyncPlayerChatEventData, AsyncPlayerPreLoginEventData,
|
||||
BedrockFormResponseEventData, CustomClickActionEventData, Event,
|
||||
InteractAction as WasmInteractAction, InventoryClickEventData,
|
||||
InventoryCloseEventData, PlayerAdvancementDoneEventData, PlayerAnimationEventData,
|
||||
PlayerArmorStandManipulateEventData, PlayerBedEnterEventData,
|
||||
PlayerBedLeaveEventData, PlayerBucketEmptyEventData, PlayerBucketEntityEventData,
|
||||
PlayerBucketFillEventData, PlayerChangeWorldEventData,
|
||||
BedrockFormResponseEventData, Event, InteractAction as WasmInteractAction,
|
||||
InventoryClickEventData, InventoryCloseEventData, PlayerAdvancementDoneEventData,
|
||||
PlayerAnimationEventData, PlayerArmorStandManipulateEventData,
|
||||
PlayerBedEnterEventData, PlayerBedLeaveEventData, PlayerBucketEmptyEventData,
|
||||
PlayerBucketEntityEventData, PlayerBucketFillEventData, PlayerChangeWorldEventData,
|
||||
PlayerChangedMainHandEventData, PlayerChangedWorldEventData,
|
||||
PlayerChannelEventData, PlayerChatEventData, PlayerCommandPreprocessEventData,
|
||||
PlayerCommandSendEventData, PlayerCustomPayloadEventData, PlayerDropItemEventData,
|
||||
@ -892,29 +890,6 @@ impl ToFromWasmEvent for BedrockFormResponseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for CustomClickActionEvent {
|
||||
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
|
||||
Event::CustomClickActionEvent(CustomClickActionEventData {
|
||||
player: state
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource"),
|
||||
id: self.id.clone(),
|
||||
payload: self.payload.as_ref().map(|p| p.to_vec()),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
|
||||
match event {
|
||||
Event::CustomClickActionEvent(data) => Self {
|
||||
player: consume_player(state, &data.player),
|
||||
id: data.id,
|
||||
payload: data.payload.map(Bytes::from),
|
||||
},
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for PlayerInteractEntityEvent {
|
||||
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
|
||||
let player = state
|
||||
|
||||
@ -5,9 +5,7 @@ use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::api::gui::PluginScreenHandler;
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::forms::Form;
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::{
|
||||
Action, AfterAction, Dialog, DialogBody, DialogInput, LinkLabel, LinkType,
|
||||
};
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::Dialog;
|
||||
use crate::{
|
||||
entity::{EntityBase, player::TitleMode},
|
||||
net::DisconnectReason,
|
||||
@ -39,10 +37,7 @@ use crate::{
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_protocol::Property;
|
||||
use pumpkin_protocol::bedrock::client::modal_form_request::CModalFormRequest;
|
||||
use pumpkin_protocol::java::client::dialog::{
|
||||
ActionButton as ProtocolActionButton, Dialog as ProtocolDialog, DialogAction,
|
||||
DialogBody as ProtocolDialogBody, DialogInput as ProtocolDialogInput, DialogLink, DialogNBT,
|
||||
};
|
||||
use pumpkin_protocol::java::client::dialog::DialogNBT;
|
||||
use pumpkin_util::permission::PermissionLvl;
|
||||
use pumpkin_util::translation::Locale;
|
||||
use std::str::FromStr;
|
||||
@ -1956,6 +1951,69 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(player.has_item_cooldown(&item_id).await)
|
||||
}
|
||||
|
||||
async fn ray_trace_block(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
max_distance: f64,
|
||||
include_fluids: bool,
|
||||
) -> wasmtime::Result<Option<pumpkin::plugin::world::RayTraceBlockResult>> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let start = player.living_entity.entity.get_eye_pos();
|
||||
let direction = player.living_entity.entity.get_looking_vector();
|
||||
let end = start + direction * max_distance;
|
||||
let world = player.living_entity.entity.world.load_full();
|
||||
|
||||
let hit = world.ray_trace_block(start, end, include_fluids);
|
||||
|
||||
Ok(hit.map(|(pos, face, hit_pos)| pumpkin::plugin::world::RayTraceBlockResult {
|
||||
pos: pumpkin::plugin::world::BlockPos {
|
||||
x: pos.0.x,
|
||||
y: pos.0.y,
|
||||
z: pos.0.z,
|
||||
},
|
||||
face: crate::plugin::loader::wasm::wasm_host::wit::v0_1::world::to_wasm_block_direction(face),
|
||||
hit_pos: to_wasm_position(hit_pos),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ray_trace_entity(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
max_distance: f64,
|
||||
) -> wasmtime::Result<Option<pumpkin::plugin::world::RayTraceEntityResult>> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let start = player.living_entity.entity.get_eye_pos();
|
||||
let direction = player.living_entity.entity.get_looking_vector();
|
||||
let end = start + direction * max_distance;
|
||||
let world = player.living_entity.entity.world.load_full();
|
||||
let self_id = player.living_entity.entity.entity_id;
|
||||
|
||||
let hits = world.ray_trace_entities(start, end);
|
||||
for (hit_entity, hit_pos, distance) in hits {
|
||||
if hit_entity.get_entity().entity_id != self_id {
|
||||
let entity_res = self
|
||||
.add_entity(hit_entity)
|
||||
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?;
|
||||
return Ok(Some(pumpkin::plugin::world::RayTraceEntityResult {
|
||||
entity: entity_res,
|
||||
hit_pos: to_wasm_position(hit_pos),
|
||||
distance,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn get_target_entity(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
max_distance: f64,
|
||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::world::Entity>>> {
|
||||
let res = self.ray_trace_entity(player, max_distance).await?;
|
||||
Ok(res.map(|r| r.entity))
|
||||
}
|
||||
|
||||
async fn get_target_block(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
@ -1982,6 +2040,16 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_target_block_exact(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
max_distance: f64,
|
||||
include_fluids: bool,
|
||||
) -> wasmtime::Result<Option<pumpkin::plugin::world::RayTraceBlockResult>> {
|
||||
self.ray_trace_block(player, max_distance, include_fluids)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn launch_projectile(
|
||||
&mut self,
|
||||
_player: Resource<Player>,
|
||||
@ -2963,129 +3031,18 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
||||
.provider
|
||||
.clone();
|
||||
|
||||
let title = text_component_from_resource(self, &dialog.title);
|
||||
let protocol_dialog = super::events::dialog::protocol_dialog_from_wasm(self, &dialog);
|
||||
|
||||
let body: Vec<_> = dialog
|
||||
.body
|
||||
.iter()
|
||||
.map(|b| match b {
|
||||
DialogBody::PlainMessage(c) => ProtocolDialogBody::PlainMessage {
|
||||
contents: text_component_from_resource(self, c),
|
||||
},
|
||||
DialogBody::Item(_i) => {
|
||||
// TODO: Map ItemStack correctly
|
||||
ProtocolDialogBody::Item { item: 0 }
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let inputs: Vec<_> = dialog
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| match i {
|
||||
DialogInput::Bool(b) => ProtocolDialogInput::Boolean {
|
||||
label: text_component_from_resource(self, &b.label),
|
||||
default_value: b.default_value,
|
||||
},
|
||||
DialogInput::Text(t) => ProtocolDialogInput::Text {
|
||||
label: text_component_from_resource(self, &t.label),
|
||||
placeholder: text_component_from_resource(self, &t.placeholder),
|
||||
default_value: t.default_value.clone(),
|
||||
},
|
||||
DialogInput::NumberRange(n) => ProtocolDialogInput::NumberRange {
|
||||
label: text_component_from_resource(self, &n.label),
|
||||
min: n.min_value,
|
||||
max: n.max_value,
|
||||
initial: n.initial_value,
|
||||
step: n.step,
|
||||
label_format: n.label_format.clone(),
|
||||
},
|
||||
DialogInput::SingleOption(s) => ProtocolDialogInput::SingleOption {
|
||||
label: text_component_from_resource(self, &s.label),
|
||||
options: s
|
||||
.options
|
||||
.iter()
|
||||
.map(|o| text_component_from_resource(self, o))
|
||||
.collect(),
|
||||
initial_index: s.initial_index,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let buttons: Vec<_> = dialog
|
||||
.buttons
|
||||
.iter()
|
||||
.map(|b| ProtocolActionButton {
|
||||
text: text_component_from_resource(self, &b.text),
|
||||
tooltip: b
|
||||
.tooltip
|
||||
.as_ref()
|
||||
.map(|t| text_component_from_resource(self, t)),
|
||||
width: b.width,
|
||||
action: match &b.action {
|
||||
Action::OpenUrl(u) => DialogAction::OpenUrl { url: u.clone() },
|
||||
Action::CustomClick(c) => DialogAction::Custom {
|
||||
id: c.id.clone(),
|
||||
payload: c.payload.clone(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let links: Vec<_> = dialog
|
||||
.links
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let label = match &l.label {
|
||||
LinkLabel::BuiltIn(t) => {
|
||||
let link_type = match t {
|
||||
LinkType::BugReport => pumpkin_protocol::LinkType::BugReport,
|
||||
LinkType::CommunityGuidelines => {
|
||||
pumpkin_protocol::LinkType::CommunityGuidelines
|
||||
}
|
||||
LinkType::Support => pumpkin_protocol::LinkType::Support,
|
||||
LinkType::Status => pumpkin_protocol::LinkType::Status,
|
||||
LinkType::Feedback => pumpkin_protocol::LinkType::Feedback,
|
||||
LinkType::Community => pumpkin_protocol::LinkType::Community,
|
||||
LinkType::Website => pumpkin_protocol::LinkType::Website,
|
||||
LinkType::Forums => pumpkin_protocol::LinkType::Forums,
|
||||
LinkType::News => pumpkin_protocol::LinkType::News,
|
||||
LinkType::Announcements => pumpkin_protocol::LinkType::Announcements,
|
||||
};
|
||||
pumpkin_protocol::Label::BuiltIn(link_type)
|
||||
}
|
||||
LinkLabel::Custom(c) => pumpkin_protocol::Label::TextComponent(Box::new(
|
||||
text_component_from_resource(self, c),
|
||||
)),
|
||||
};
|
||||
DialogLink {
|
||||
label,
|
||||
url: l.url.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let protocol_dialog = ProtocolDialog {
|
||||
r#type: match dialog.type_ {
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogType::Notice => "minecraft:notice".to_string(),
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogType::Confirmation => "minecraft:confirmation".to_string(),
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogType::MultiAction => "minecraft:multi_action".to_string(),
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogType::DialogList => "minecraft:dialog_list".to_string(),
|
||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_dialogs::DialogType::ServerLinks => "minecraft:server_links".to_string(),
|
||||
},
|
||||
title,
|
||||
body,
|
||||
inputs,
|
||||
buttons,
|
||||
links,
|
||||
exit_action: None, // TODO
|
||||
after_action: dialog.after_action.map(|a| match a {
|
||||
AfterAction::Peek => "peek".to_string(),
|
||||
AfterAction::Pop => "pop".to_string(),
|
||||
}),
|
||||
can_close_with_escape: dialog.can_close_with_escape,
|
||||
external_title: dialog.external_title.as_ref().map(|t| text_component_from_resource(self, t)),
|
||||
};
|
||||
if let Some(server) = player.world().server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::dialog::dialog_show::DialogShowEvent::new(
|
||||
player.clone(),
|
||||
protocol_dialog.clone(),
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let crate::net::ClientPlatform::Java(client) = player.client.as_ref() {
|
||||
match client.connection_state.load() {
|
||||
@ -3127,6 +3084,16 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
||||
.provider
|
||||
.clone();
|
||||
|
||||
if let Some(server) = player.world().server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::dialog::dialog_clear::DialogClearEvent::new(
|
||||
player.clone(),
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let crate::net::ClientPlatform::Java(client) = player.client.as_ref() {
|
||||
match client.connection_state.load() {
|
||||
pumpkin_protocol::ConnectionState::Config => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
@ -16,6 +17,7 @@ use pumpkin_util::text::{
|
||||
color::{self, Color},
|
||||
hover::HoverEvent,
|
||||
};
|
||||
use pumpkin_util::translation::Locale;
|
||||
|
||||
// --- Trapping Helpers ---
|
||||
impl PluginHostState {
|
||||
@ -92,11 +94,101 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
for r in with {
|
||||
components.push(self.take_text(&r)?.provider);
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let tc = InternalTextComponent::translate(key, components);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn translate_cross(
|
||||
&mut self,
|
||||
java_key: String,
|
||||
bedrock_key: String,
|
||||
with: Vec<Resource<TextComponent>>,
|
||||
) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let mut components = Vec::with_capacity(with.len());
|
||||
for r in with {
|
||||
components.push(self.take_text(&r)?.provider);
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
let tc = InternalTextComponent::translate_cross(java_key, bedrock_key, components);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn entity_names(
|
||||
&mut self,
|
||||
selector: String,
|
||||
separator: Option<String>,
|
||||
) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let tc = InternalTextComponent::entity_names(selector, separator);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn keybind(&mut self, keybind: String) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let tc = InternalTextComponent::keybind(keybind);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn custom(
|
||||
&mut self,
|
||||
namespace: String,
|
||||
key: String,
|
||||
locale: String,
|
||||
with: Vec<Resource<TextComponent>>,
|
||||
) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let loc = Locale::from_str(&locale).unwrap_or(Locale::EnUs);
|
||||
let mut components = Vec::with_capacity(with.len());
|
||||
for r in with {
|
||||
components.push(self.take_text(&r)?.provider);
|
||||
}
|
||||
let tc = InternalTextComponent::custom(namespace, key, loc, components);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn from_legacy_string(
|
||||
&mut self,
|
||||
input: String,
|
||||
) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let tc = InternalTextComponent::from_legacy_string(&input);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn from_legacy_string_with_code(
|
||||
&mut self,
|
||||
input: String,
|
||||
code_symbol: char,
|
||||
) -> wasmtime::Result<Resource<TextComponent>> {
|
||||
let tc = InternalTextComponent::from_legacy_string_with_code(&input, code_symbol);
|
||||
self.add_text_component(tc)
|
||||
.map_err(|_| wasmtime::Error::msg("Failed to add text component"))
|
||||
}
|
||||
|
||||
async fn from_json(
|
||||
&mut self,
|
||||
json: String,
|
||||
) -> wasmtime::Result<Result<Resource<TextComponent>, String>> {
|
||||
match serde_json::from_str::<InternalTextComponent>(&json) {
|
||||
Ok(tc) => match self.add_text_component(tc) {
|
||||
Ok(res) => Ok(Ok(res)),
|
||||
Err(err) => Ok(Err(err.to_string())),
|
||||
},
|
||||
Err(err) => Ok(Err(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_json(
|
||||
&mut self,
|
||||
text_component: Resource<TextComponent>,
|
||||
) -> wasmtime::Result<String> {
|
||||
let tc = &self.get_text_ref(&text_component)?.provider;
|
||||
Ok(serde_json::to_string(tc).unwrap_or_default())
|
||||
}
|
||||
|
||||
async fn add_child(
|
||||
&mut self,
|
||||
text_component: Resource<TextComponent>,
|
||||
@ -104,7 +196,6 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
) -> wasmtime::Result<()> {
|
||||
let child_tc = self.take_text(&child)?.provider;
|
||||
let parent = self.get_text_mut(&text_component)?;
|
||||
// Cloning here as noted in your TODO until builder pattern supports &mut self
|
||||
parent.provider = parent.provider.clone().add_child(child_tc);
|
||||
Ok(())
|
||||
}
|
||||
@ -141,6 +232,17 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
.into_vec())
|
||||
}
|
||||
|
||||
async fn to_pretty_console(
|
||||
&mut self,
|
||||
text_component: Resource<TextComponent>,
|
||||
) -> wasmtime::Result<String> {
|
||||
Ok(self
|
||||
.get_text_ref(&text_component)?
|
||||
.provider
|
||||
.clone()
|
||||
.to_pretty_console())
|
||||
}
|
||||
|
||||
async fn color_named(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
@ -161,6 +263,37 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn gradient_named(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
colors: Vec<NamedColor>,
|
||||
) -> wasmtime::Result<()> {
|
||||
let mapped: Vec<_> = colors.into_iter().map(map_named_color).collect();
|
||||
let parent = self.get_text_mut(&res)?;
|
||||
parent.provider = parent.provider.clone().gradient_named(&mapped);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn gradient(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
colors: Vec<RgbColor>,
|
||||
) -> wasmtime::Result<()> {
|
||||
let mapped: Vec<_> = colors
|
||||
.into_iter()
|
||||
.map(|c| color::RGBColor::new(c.r, c.g, c.b))
|
||||
.collect();
|
||||
let parent = self.get_text_mut(&res)?;
|
||||
parent.provider = parent.provider.clone().gradient(&mapped);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rainbow(&mut self, res: Resource<TextComponent>) -> wasmtime::Result<()> {
|
||||
let parent = self.get_text_mut(&res)?;
|
||||
parent.provider = parent.provider.clone().rainbow();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn bold(&mut self, res: Resource<TextComponent>, value: bool) -> wasmtime::Result<()> {
|
||||
self.get_text_mut(&res)?.provider.0.style.bold = Some(value);
|
||||
Ok(())
|
||||
@ -233,6 +366,17 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn click_open_file(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<()> {
|
||||
self.get_text_mut(&res)?.provider.0.style.click_event = Some(ClickEvent::OpenFile {
|
||||
path: Cow::Owned(path),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn click_run_command(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
@ -255,6 +399,16 @@ impl pumpkin::plugin::text::HostTextComponent for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn click_change_page(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
page: u32,
|
||||
) -> wasmtime::Result<()> {
|
||||
self.get_text_mut(&res)?.provider.0.style.click_event =
|
||||
Some(ClickEvent::ChangePage { page });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn click_copy_to_clipboard(
|
||||
&mut self,
|
||||
res: Resource<TextComponent>,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use pumpkin_data::block_properties::NoteblockInstrument as InternalNoteblockInstrument;
|
||||
use pumpkin_data::block_state::PistonBehavior;
|
||||
use pumpkin_data::{BlockDirection as InternalBlockDirection, BlockStateId};
|
||||
use pumpkin_data::{BlockDirection as InternalBlockDirection, BlockId, BlockStateId};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::chunk::ChunkHeightmapType;
|
||||
use pumpkin_world::chunk::io::Dirtiable;
|
||||
@ -64,11 +64,13 @@ use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::game_rul
|
||||
GameRule as WitGameRule, GameRuleValue as WitGameRuleValue,
|
||||
};
|
||||
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, BlockStateInfo as WitBlockStateInfo,
|
||||
BoundingBox as WitBoundingBox, Chunk as WitChunk,
|
||||
NoteblockInstrument as WitNoteblockInstrument, PistonBehavior as WitPistonBehavior,
|
||||
WorldBorder as WitWorldBorder,
|
||||
Block as WitBlock, BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType,
|
||||
BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockState as WitBlockState,
|
||||
BlockStateInfo as WitBlockStateInfo, BoundingBox as WitBoundingBox, Chunk as WitChunk,
|
||||
Flammable as WitFlammable, NoteblockInstrument as WitNoteblockInstrument,
|
||||
PistonBehavior as WitPistonBehavior, RayTraceBlockResult as WitRayTraceBlockResult,
|
||||
RayTraceEntityResult as WitRayTraceEntityResult, WorldBorder as WitWorldBorder,
|
||||
WorldSpawnLocation as WitWorldSpawnLocation,
|
||||
};
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
state::{
|
||||
@ -154,6 +156,93 @@ pub(crate) const fn to_wit_bounding_box(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_wit_block(block: &pumpkin_data::Block) -> WitBlock {
|
||||
WitBlock {
|
||||
id: block.id.as_u16(),
|
||||
name: block.name.to_string(),
|
||||
hardness: block.hardness,
|
||||
blast_resistance: block.blast_resistance,
|
||||
map_color: block.map_color,
|
||||
slipperiness: block.slipperiness,
|
||||
velocity_multiplier: block.velocity_multiplier,
|
||||
jump_velocity_multiplier: block.jump_velocity_multiplier,
|
||||
item_id: block.item_id,
|
||||
default_state_id: block.default_state.id.as_u16(),
|
||||
state_ids: block.states.iter().map(|s| s.id.as_u16()).collect(),
|
||||
is_solid: block.is_solid(),
|
||||
is_air: block.is_air(),
|
||||
is_flammable: block.flammable.is_some(),
|
||||
flammable: block.flammable.as_ref().map(|f| WitFlammable {
|
||||
spread_chance: f.spread_chance,
|
||||
burn_chance: f.burn_chance,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_wit_block_state(
|
||||
state: &pumpkin_data::BlockState,
|
||||
pos: Option<&BlockPos>,
|
||||
) -> WitBlockState {
|
||||
let dummy_pos = BlockPos::new(0, 0, 0);
|
||||
let internal_pos = pos.unwrap_or(&dummy_pos);
|
||||
let block = pumpkin_data::Block::from_state_id(state.id);
|
||||
let properties = block
|
||||
.properties(state.id)
|
||||
.map(|p| {
|
||||
p.to_props()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
WitBlockState {
|
||||
id: state.id.as_u16(),
|
||||
block_id: block.id.as_u16(),
|
||||
block_name: block.name.to_string(),
|
||||
luminance: state.luminance,
|
||||
opacity: state.opacity,
|
||||
hardness: state.hardness,
|
||||
is_air: state.is_air(),
|
||||
is_liquid: state.is_liquid(),
|
||||
is_solid: state.is_solid(),
|
||||
is_full_cube: state.is_full_cube(),
|
||||
has_random_ticks: state.has_random_ticks(),
|
||||
piston_behavior: match state.piston_behavior {
|
||||
PistonBehavior::Normal => WitPistonBehavior::Normal,
|
||||
PistonBehavior::Destroy => WitPistonBehavior::Destroy,
|
||||
PistonBehavior::Block => WitPistonBehavior::Block,
|
||||
PistonBehavior::Ignore => WitPistonBehavior::Ignore,
|
||||
PistonBehavior::PushOnly => WitPistonBehavior::PushOnly,
|
||||
},
|
||||
burnable: state.burnable(),
|
||||
tool_required: state.tool_required(),
|
||||
sided_transparency: state.sided_transparency(),
|
||||
replaceable: state.replaceable(),
|
||||
is_solid_block: state.is_solid_block(),
|
||||
block_entity_type: state.block_entity_type,
|
||||
instrument: to_wit_noteblock_instrument(state.instrument),
|
||||
collision_shapes: state
|
||||
.get_block_collision_shapes_at(internal_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
outline_shapes: state
|
||||
.get_block_outline_shapes_at(internal_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
||||
up_side_solid: state.is_side_solid(InternalBlockDirection::Up),
|
||||
north_side_solid: state.is_side_solid(InternalBlockDirection::North),
|
||||
south_side_solid: state.is_side_solid(InternalBlockDirection::South),
|
||||
west_side_solid: state.is_side_solid(InternalBlockDirection::West),
|
||||
east_side_solid: state.is_side_solid(InternalBlockDirection::East),
|
||||
down_center_solid: state.is_center_solid(InternalBlockDirection::Down),
|
||||
up_center_solid: state.is_center_solid(InternalBlockDirection::Up),
|
||||
map_color: block.map_color,
|
||||
properties,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Trapping Helpers ---
|
||||
impl PluginHostState {
|
||||
pub(crate) fn get_world_res(&self, res: &Resource<World>) -> wasmtime::Result<&WorldResource> {
|
||||
@ -331,6 +420,141 @@ impl pumpkin::plugin::world::Host for PluginHostState {
|
||||
}));
|
||||
Ok(result.ok())
|
||||
}
|
||||
|
||||
async fn get_block_by_id(&mut self, id: u16) -> wasmtime::Result<Option<WitBlock>> {
|
||||
let block_id = BlockId::new(id);
|
||||
Ok(block_id.map(|id| to_wit_block(pumpkin_data::Block::from_id(id))))
|
||||
}
|
||||
|
||||
async fn get_block_by_name(&mut self, name: String) -> wasmtime::Result<Option<WitBlock>> {
|
||||
Ok(pumpkin_data::Block::from_name(&name).map(to_wit_block))
|
||||
}
|
||||
|
||||
async fn get_all_blocks(&mut self) -> wasmtime::Result<Vec<WitBlock>> {
|
||||
let mut blocks = Vec::with_capacity(BlockId::COUNT as usize);
|
||||
for raw_id in 0..BlockId::COUNT {
|
||||
if let Some(id) = BlockId::new(raw_id) {
|
||||
blocks.push(to_wit_block(pumpkin_data::Block::from_id(id)));
|
||||
}
|
||||
}
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
async fn get_all_block_names(&mut self) -> wasmtime::Result<Vec<String>> {
|
||||
let mut names = Vec::with_capacity(BlockId::COUNT as usize);
|
||||
for raw_id in 0..BlockId::COUNT {
|
||||
if let Some(id) = BlockId::new(raw_id) {
|
||||
names.push(pumpkin_data::Block::from_id(id).name.to_string());
|
||||
}
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
async fn get_block_count(&mut self) -> wasmtime::Result<u32> {
|
||||
Ok(BlockId::COUNT as u32)
|
||||
}
|
||||
|
||||
async fn get_block_state_count(&mut self) -> wasmtime::Result<u32> {
|
||||
Ok(BlockStateId::COUNT as u32)
|
||||
}
|
||||
|
||||
async fn get_states_for_block(
|
||||
&mut self,
|
||||
block: WitBlock,
|
||||
) -> wasmtime::Result<Vec<WitBlockState>> {
|
||||
let block_id = BlockId::new_or_air(block.id);
|
||||
let block_ref = pumpkin_data::Block::from_id(block_id);
|
||||
Ok(block_ref
|
||||
.states
|
||||
.iter()
|
||||
.map(|s| to_wit_block_state(s, None))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_states_for_block_id(
|
||||
&mut self,
|
||||
block_id: u16,
|
||||
) -> wasmtime::Result<Vec<WitBlockState>> {
|
||||
let Some(id) = BlockId::new(block_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let block_ref = pumpkin_data::Block::from_id(id);
|
||||
Ok(block_ref
|
||||
.states
|
||||
.iter()
|
||||
.map(|s| to_wit_block_state(s, None))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_state_ids_for_block_id(&mut self, block_id: u16) -> wasmtime::Result<Vec<u16>> {
|
||||
let Some(id) = BlockId::new(block_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let block_ref = pumpkin_data::Block::from_id(id);
|
||||
Ok(block_ref.states.iter().map(|s| s.id.as_u16()).collect())
|
||||
}
|
||||
|
||||
async fn get_block_properties(
|
||||
&mut self,
|
||||
state_id: u16,
|
||||
) -> wasmtime::Result<Vec<(String, String)>> {
|
||||
let bsid = BlockStateId::new_or_air(state_id);
|
||||
let block = pumpkin_data::Block::from_state_id(bsid);
|
||||
let props = block
|
||||
.properties(bsid)
|
||||
.map(|p| {
|
||||
p.to_props()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Ok(props)
|
||||
}
|
||||
|
||||
async fn get_block_from_state_id(
|
||||
&mut self,
|
||||
state_id: u16,
|
||||
) -> wasmtime::Result<Option<WitBlock>> {
|
||||
let bsid = BlockStateId::new(state_id);
|
||||
Ok(bsid.map(|id| to_wit_block(pumpkin_data::Block::from_state_id(id))))
|
||||
}
|
||||
|
||||
async fn get_block_from_state(&mut self, state: WitBlockState) -> wasmtime::Result<WitBlock> {
|
||||
let bsid = BlockStateId::new_or_air(state.id);
|
||||
Ok(to_wit_block(pumpkin_data::Block::from_state_id(bsid)))
|
||||
}
|
||||
|
||||
async fn get_default_state_from_block(
|
||||
&mut self,
|
||||
block: WitBlock,
|
||||
) -> wasmtime::Result<WitBlockState> {
|
||||
let block_id = BlockId::new_or_air(block.id);
|
||||
let block_ref = pumpkin_data::Block::from_id(block_id);
|
||||
Ok(to_wit_block_state(block_ref.default_state, None))
|
||||
}
|
||||
|
||||
async fn get_default_state_from_block_id(
|
||||
&mut self,
|
||||
block_id: u16,
|
||||
) -> wasmtime::Result<Option<WitBlockState>> {
|
||||
let block_id = BlockId::new(block_id);
|
||||
Ok(block_id.map(|id| {
|
||||
let block = pumpkin_data::Block::from_id(id);
|
||||
to_wit_block_state(block.default_state, None)
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_block_state_by_id(
|
||||
&mut self,
|
||||
state_id: u16,
|
||||
) -> wasmtime::Result<Option<WitBlockState>> {
|
||||
let bsid = BlockStateId::new(state_id);
|
||||
Ok(bsid.map(|id| {
|
||||
let state = pumpkin_data::BlockState::from_id(id);
|
||||
to_wit_block_state(state, None)
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl pumpkin::plugin::particles::Host for PluginHostState {}
|
||||
impl pumpkin::plugin::sounds::Host for PluginHostState {}
|
||||
@ -344,6 +568,13 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
.to_string())
|
||||
}
|
||||
|
||||
async fn get_border(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
) -> wasmtime::Result<Resource<WitWorldBorder>> {
|
||||
self.get_world_border(world).await
|
||||
}
|
||||
|
||||
async fn get_world_border(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
@ -352,6 +583,22 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
self.add_world_border(world_res.provider.clone())
|
||||
}
|
||||
|
||||
async fn get_spawn_location(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
) -> wasmtime::Result<WitWorldSpawnLocation> {
|
||||
let (pos, yaw, pitch) = self.get_world_res(&world)?.provider.get_spawn_location();
|
||||
Ok(WitWorldSpawnLocation {
|
||||
pos: WitBlockPos {
|
||||
x: pos.0.x,
|
||||
y: pos.0.y,
|
||||
z: pos.0.z,
|
||||
},
|
||||
yaw,
|
||||
pitch,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_chunk(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
@ -396,49 +643,78 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
let world_ref = self.get_world_res(&world)?;
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
let state = world_ref.provider.get_block_state(&internal_pos);
|
||||
Ok(to_wit_block_state(state, Some(&internal_pos)))
|
||||
}
|
||||
|
||||
Ok(WitBlockState {
|
||||
id: state.id.as_u16(),
|
||||
luminance: state.luminance,
|
||||
opacity: state.opacity,
|
||||
hardness: state.hardness,
|
||||
is_air: state.is_air(),
|
||||
is_liquid: state.is_liquid(),
|
||||
is_solid: state.is_solid(),
|
||||
is_full_cube: state.is_full_cube(),
|
||||
has_random_ticks: state.has_random_ticks(),
|
||||
piston_behavior: match state.piston_behavior {
|
||||
PistonBehavior::Normal => WitPistonBehavior::Normal,
|
||||
PistonBehavior::Destroy => WitPistonBehavior::Destroy,
|
||||
PistonBehavior::Block => WitPistonBehavior::Block,
|
||||
PistonBehavior::Ignore => WitPistonBehavior::Ignore,
|
||||
PistonBehavior::PushOnly => WitPistonBehavior::PushOnly,
|
||||
},
|
||||
burnable: state.burnable(),
|
||||
tool_required: state.tool_required(),
|
||||
sided_transparency: state.sided_transparency(),
|
||||
replaceable: state.replaceable(),
|
||||
is_solid_block: state.is_solid_block(),
|
||||
block_entity_type: state.block_entity_type,
|
||||
instrument: to_wit_noteblock_instrument(state.instrument),
|
||||
collision_shapes: state
|
||||
.get_block_collision_shapes_at(&internal_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
outline_shapes: state
|
||||
.get_block_outline_shapes_at(&internal_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
||||
up_side_solid: state.is_side_solid(InternalBlockDirection::Up),
|
||||
north_side_solid: state.is_side_solid(InternalBlockDirection::North),
|
||||
south_side_solid: state.is_side_solid(InternalBlockDirection::South),
|
||||
west_side_solid: state.is_side_solid(InternalBlockDirection::West),
|
||||
east_side_solid: state.is_side_solid(InternalBlockDirection::East),
|
||||
down_center_solid: state.is_center_solid(InternalBlockDirection::Down),
|
||||
up_center_solid: state.is_center_solid(InternalBlockDirection::Up),
|
||||
map_color: pumpkin_data::Block::from_state_id(state.id).map_color,
|
||||
})
|
||||
async fn get_block(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
) -> wasmtime::Result<WitBlock> {
|
||||
let world_ref = self.get_world_res(&world)?;
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
let state = world_ref.provider.get_block_state(&internal_pos);
|
||||
let block = pumpkin_data::Block::from_state_id(state.id);
|
||||
Ok(to_wit_block(block))
|
||||
}
|
||||
|
||||
async fn get_block_id(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
) -> wasmtime::Result<u16> {
|
||||
let world_ref = self.get_world_res(&world)?;
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
let state = world_ref.provider.get_block_state(&internal_pos);
|
||||
Ok(pumpkin_data::BlockId::from_state_id(state.id).as_u16())
|
||||
}
|
||||
|
||||
async fn set_block(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
block: WitBlock,
|
||||
update_flags: WitBlockFlags,
|
||||
) -> wasmtime::Result<()> {
|
||||
let block_id = BlockId::new_or_air(block.id);
|
||||
let default_state_id = pumpkin_data::Block::from_id(block_id)
|
||||
.default_state
|
||||
.id
|
||||
.as_u16();
|
||||
self.set_block_state(world, pos, default_state_id, update_flags)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_block_by_id(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
block_id: u16,
|
||||
update_flags: WitBlockFlags,
|
||||
) -> wasmtime::Result<()> {
|
||||
let Some(id) = BlockId::new(block_id) else {
|
||||
return Err(wasmtime::Error::msg("Invalid BlockId"));
|
||||
};
|
||||
let default_state_id = pumpkin_data::Block::from_id(id).default_state.id.as_u16();
|
||||
self.set_block_state(world, pos, default_state_id, update_flags)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_block_by_name(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
name: String,
|
||||
update_flags: WitBlockFlags,
|
||||
) -> wasmtime::Result<bool> {
|
||||
if let Some(block) = pumpkin_data::Block::from_name(&name) {
|
||||
let default_state_id = block.default_state.id.as_u16();
|
||||
self.set_block_state(world, pos, default_state_id, update_flags)
|
||||
.await?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_block_state(
|
||||
@ -866,6 +1142,77 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ray_trace_block(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
start: WitPosition,
|
||||
end: WitPosition,
|
||||
include_fluids: bool,
|
||||
) -> wasmtime::Result<Option<WitRayTraceBlockResult>> {
|
||||
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.ray_trace_block(start_pos, end_pos, include_fluids);
|
||||
Ok(res.map(|(pos, face, hit_pos)| WitRayTraceBlockResult {
|
||||
pos: WitBlockPos {
|
||||
x: pos.0.x,
|
||||
y: pos.0.y,
|
||||
z: pos.0.z,
|
||||
},
|
||||
face: to_wasm_block_direction(face),
|
||||
hit_pos: super::events::to_wasm_position(hit_pos),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ray_trace_entity(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
start: WitPosition,
|
||||
end: WitPosition,
|
||||
) -> wasmtime::Result<Option<WitRayTraceEntityResult>> {
|
||||
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);
|
||||
if let Some((entity, hit_pos, distance)) =
|
||||
world_provider.ray_trace_entity(start_pos, end_pos)
|
||||
{
|
||||
let entity_res = self
|
||||
.add_entity(entity)
|
||||
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?;
|
||||
Ok(Some(WitRayTraceEntityResult {
|
||||
entity: entity_res,
|
||||
hit_pos: super::events::to_wasm_position(hit_pos),
|
||||
distance,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn ray_trace_entities(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
start: WitPosition,
|
||||
end: WitPosition,
|
||||
) -> wasmtime::Result<Vec<WitRayTraceEntityResult>> {
|
||||
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 hits = world_provider.ray_trace_entities(start_pos, end_pos);
|
||||
let mut results = Vec::with_capacity(hits.len());
|
||||
for (entity, hit_pos, distance) in hits {
|
||||
let entity_res = self
|
||||
.add_entity(entity)
|
||||
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?;
|
||||
results.push(WitRayTraceEntityResult {
|
||||
entity: entity_res,
|
||||
hit_pos: super::events::to_wasm_position(hit_pos),
|
||||
distance,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn get_block_entity(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
@ -1097,49 +1444,52 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
.unwrap_or(BlockStateId::AIR);
|
||||
let state = id.to_state();
|
||||
let world_pos = BlockPos::new(chunk_data.x * 16 + pos.x, pos.y, chunk_data.z * 16 + pos.z);
|
||||
Ok(to_wit_block_state(state, Some(&world_pos)))
|
||||
}
|
||||
|
||||
Ok(WitBlockState {
|
||||
id: id.as_u16(),
|
||||
luminance: state.luminance,
|
||||
opacity: state.opacity,
|
||||
hardness: state.hardness,
|
||||
is_air: state.is_air(),
|
||||
is_liquid: state.is_liquid(),
|
||||
is_solid: state.is_solid(),
|
||||
is_full_cube: state.is_full_cube(),
|
||||
has_random_ticks: state.has_random_ticks(),
|
||||
piston_behavior: match state.piston_behavior {
|
||||
PistonBehavior::Normal => WitPistonBehavior::Normal,
|
||||
PistonBehavior::Destroy => WitPistonBehavior::Destroy,
|
||||
PistonBehavior::Block => WitPistonBehavior::Block,
|
||||
PistonBehavior::Ignore => WitPistonBehavior::Ignore,
|
||||
PistonBehavior::PushOnly => WitPistonBehavior::PushOnly,
|
||||
},
|
||||
burnable: state.burnable(),
|
||||
tool_required: state.tool_required(),
|
||||
sided_transparency: state.sided_transparency(),
|
||||
replaceable: state.replaceable(),
|
||||
is_solid_block: state.is_solid_block(),
|
||||
block_entity_type: state.block_entity_type,
|
||||
instrument: to_wit_noteblock_instrument(state.instrument),
|
||||
collision_shapes: state
|
||||
.get_block_collision_shapes_at(&world_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
outline_shapes: state
|
||||
.get_block_outline_shapes_at(&world_pos)
|
||||
.map(to_wit_bounding_box)
|
||||
.collect(),
|
||||
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
||||
up_side_solid: state.is_side_solid(InternalBlockDirection::Up),
|
||||
north_side_solid: state.is_side_solid(InternalBlockDirection::North),
|
||||
south_side_solid: state.is_side_solid(InternalBlockDirection::South),
|
||||
west_side_solid: state.is_side_solid(InternalBlockDirection::West),
|
||||
east_side_solid: state.is_side_solid(InternalBlockDirection::East),
|
||||
down_center_solid: state.is_center_solid(InternalBlockDirection::Down),
|
||||
up_center_solid: state.is_center_solid(InternalBlockDirection::Up),
|
||||
map_color: pumpkin_data::Block::from_state_id(state.id).map_color,
|
||||
})
|
||||
async fn get_block(
|
||||
&mut self,
|
||||
chunk: Resource<WitChunk>,
|
||||
pos: WitBlockPos,
|
||||
) -> wasmtime::Result<WitBlock> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let id = chunk_data
|
||||
.section
|
||||
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
||||
.unwrap_or(BlockStateId::AIR);
|
||||
let block = pumpkin_data::Block::from_state_id(id);
|
||||
Ok(to_wit_block(block))
|
||||
}
|
||||
|
||||
async fn set_block(
|
||||
&mut self,
|
||||
chunk: Resource<WitChunk>,
|
||||
pos: WitBlockPos,
|
||||
block: WitBlock,
|
||||
) -> wasmtime::Result<()> {
|
||||
let block_id = BlockId::new_or_air(block.id);
|
||||
let default_state_id = pumpkin_data::Block::from_id(block_id)
|
||||
.default_state
|
||||
.id
|
||||
.as_u16();
|
||||
self.set_block_state(chunk, pos, default_state_id).await
|
||||
}
|
||||
|
||||
async fn set_block_by_id(
|
||||
&mut self,
|
||||
chunk: Resource<WitChunk>,
|
||||
pos: WitBlockPos,
|
||||
block_id: u16,
|
||||
) -> wasmtime::Result<()> {
|
||||
let Some(id) = BlockId::new(block_id) else {
|
||||
return Err(wasmtime::Error::msg("Invalid BlockId"));
|
||||
};
|
||||
let default_state_id = pumpkin_data::Block::from_id(id).default_state.id.as_u16();
|
||||
self.set_block_state(chunk, pos, default_state_id).await
|
||||
}
|
||||
|
||||
async fn set_block_state(
|
||||
@ -1355,6 +1705,17 @@ impl pumpkin::plugin::world::HostWorldBorder for PluginHostState {
|
||||
Ok(border_res.provider.worldborder.lock().await.center_z)
|
||||
}
|
||||
|
||||
async fn get_center(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<WitPosition> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
let guard = border_res.provider.worldborder.lock().await;
|
||||
Ok(super::events::to_wasm_position(
|
||||
pumpkin_util::math::vector3::Vector3::new(guard.center_x, 0.0, guard.center_z),
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_center(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
@ -1372,6 +1733,10 @@ impl pumpkin::plugin::world::HostWorldBorder for PluginHostState {
|
||||
Ok(border_res.provider.worldborder.lock().await.new_diameter)
|
||||
}
|
||||
|
||||
async fn get_size(&mut self, border: Resource<WitWorldBorder>) -> wasmtime::Result<f64> {
|
||||
self.get_diameter(border).await
|
||||
}
|
||||
|
||||
async fn set_diameter(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
@ -1388,6 +1753,41 @@ impl pumpkin::plugin::world::HostWorldBorder for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_size(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
size: f64,
|
||||
) -> wasmtime::Result<()> {
|
||||
self.set_diameter(border, size, None).await
|
||||
}
|
||||
|
||||
async fn set_size_transition(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
new_size: f64,
|
||||
time_seconds: u64,
|
||||
) -> wasmtime::Result<()> {
|
||||
let speed_millis = time_seconds.saturating_mul(1000);
|
||||
self.set_diameter(border, new_size, Some(speed_millis))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_target_diameter(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<f64> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
Ok(border_res.provider.worldborder.lock().await.new_diameter)
|
||||
}
|
||||
|
||||
async fn get_target_speed(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<i64> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
Ok(border_res.provider.worldborder.lock().await.speed)
|
||||
}
|
||||
|
||||
async fn get_warning_distance(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
@ -1434,6 +1834,76 @@ impl pumpkin::plugin::world::HostWorldBorder for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_warning_time(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<i32> {
|
||||
self.get_warning_delay(border).await
|
||||
}
|
||||
|
||||
async fn set_warning_time(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
time: i32,
|
||||
) -> wasmtime::Result<()> {
|
||||
self.set_warning_delay(border, time).await
|
||||
}
|
||||
|
||||
async fn get_damage_buffer(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<f64> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
Ok(f64::from(
|
||||
border_res.provider.worldborder.lock().await.buffer,
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_damage_buffer(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
buffer: f64,
|
||||
) -> wasmtime::Result<()> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
border_res
|
||||
.provider
|
||||
.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.set_damage_buffer(buffer as f32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_damage_amount(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
) -> wasmtime::Result<f64> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
Ok(f64::from(
|
||||
border_res
|
||||
.provider
|
||||
.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.damage_per_block,
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_damage_amount(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
damage: f64,
|
||||
) -> wasmtime::Result<()> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
border_res
|
||||
.provider
|
||||
.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.set_damage_per_block(damage as f32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn contains(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
@ -1444,6 +1914,27 @@ impl pumpkin::plugin::world::HostWorldBorder for PluginHostState {
|
||||
Ok(border_res.provider.worldborder.lock().await.contains(x, z))
|
||||
}
|
||||
|
||||
async fn contains_pos(
|
||||
&mut self,
|
||||
border: Resource<WitWorldBorder>,
|
||||
pos: WitPosition,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
Ok(border_res
|
||||
.provider
|
||||
.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.contains(pos.0, pos.2))
|
||||
}
|
||||
|
||||
async fn reset(&mut self, border: Resource<WitWorldBorder>) -> wasmtime::Result<()> {
|
||||
let border_res = self.get_world_border_res(&border)?;
|
||||
let world = border_res.provider.clone();
|
||||
world.worldborder.lock().await.reset(&world);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<WitWorldBorder>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<WorldBorderResource>(Resource::new_own(rep.rep()))
|
||||
|
||||
@ -100,6 +100,38 @@ impl Worldborder {
|
||||
world.broadcast_packet_all(&CSetBorderWarningDistance::new(self.warning_blocks.into()));
|
||||
}
|
||||
|
||||
pub const fn set_damage_buffer(&mut self, buffer: f32) {
|
||||
self.buffer = buffer;
|
||||
}
|
||||
|
||||
pub const fn set_damage_per_block(&mut self, damage: f32) {
|
||||
self.damage_per_block = damage;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, world: &World) {
|
||||
self.center_x = 0.0;
|
||||
self.center_z = 0.0;
|
||||
self.old_diameter = 29_999_984.0;
|
||||
self.new_diameter = 29_999_984.0;
|
||||
self.speed = 0;
|
||||
self.portal_teleport_boundary = 29_999_984;
|
||||
self.warning_blocks = 5;
|
||||
self.warning_time = 15;
|
||||
self.damage_per_block = 0.2;
|
||||
self.buffer = 5.0;
|
||||
|
||||
world.broadcast_packet_all(&CInitializeWorldBorder::new(
|
||||
self.center_x,
|
||||
self.center_z,
|
||||
self.old_diameter,
|
||||
self.new_diameter,
|
||||
self.speed.into(),
|
||||
self.portal_teleport_boundary.into(),
|
||||
self.warning_blocks.into(),
|
||||
self.warning_time.into(),
|
||||
));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn contains(&self, x: f64, z: f64) -> bool {
|
||||
let half = self.new_diameter / 2.0;
|
||||
|
||||
@ -460,6 +460,17 @@ impl World {
|
||||
.unwrap_or("world")
|
||||
}
|
||||
|
||||
/// Returns the configured shared world spawn block position and rotation.
|
||||
#[must_use]
|
||||
pub fn get_spawn_location(&self) -> (BlockPos, f32, f32) {
|
||||
let level_info = self.level_info.load();
|
||||
(
|
||||
BlockPos::new(level_info.spawn_x, level_info.spawn_y, level_info.spawn_z),
|
||||
level_info.spawn_yaw,
|
||||
level_info.spawn_pitch,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) {
|
||||
for entity in self.entities.load().iter() {
|
||||
self.save_entity(entity).await;
|
||||
@ -5469,6 +5480,12 @@ impl World {
|
||||
&CWorldEvent::new(world_event as i32, position, data, false),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_block_destroy_stage(&self, entity_id: i32, location: BlockPos, stage: i8) {
|
||||
let chunk_pos = location.chunk_position();
|
||||
let packet = CSetBlockDestroyStage::new(entity_id.into(), location, stage);
|
||||
self.broadcast_to_chunk(chunk_pos, &packet);
|
||||
}
|
||||
#[must_use]
|
||||
pub fn is_valid(dest: BlockPos) -> bool {
|
||||
Self::is_valid_horizontally(dest) && Self::is_valid_vertically(dest.0.y)
|
||||
@ -6017,12 +6034,13 @@ impl World {
|
||||
});
|
||||
}
|
||||
|
||||
fn intersects_aabb_with_direction(
|
||||
#[must_use]
|
||||
pub fn intersects_aabb_with_hit(
|
||||
from: Vector3<f64>,
|
||||
to: Vector3<f64>,
|
||||
min: Vector3<f64>,
|
||||
max: Vector3<f64>,
|
||||
) -> Option<BlockDirection> {
|
||||
) -> Option<(f64, BlockDirection, Vector3<f64>)> {
|
||||
let dir = to.sub(&from);
|
||||
let mut tmin: f64 = 0.0;
|
||||
let mut tmax: f64 = 1.0;
|
||||
@ -6031,7 +6049,7 @@ impl World {
|
||||
let mut hit_is_min = false;
|
||||
|
||||
macro_rules! check_axis {
|
||||
($axis:ident, $dir_axis:ident, $min_axis:ident, $max_axis:ident, $direction_min:expr, $direction_max:expr) => {{
|
||||
($axis:ident, $dir_axis:ident, $min_axis:ident, $max_axis:ident) => {{
|
||||
if dir.$dir_axis.abs() < 1e-8 {
|
||||
if from.$dir_axis < min.$min_axis || from.$dir_axis > max.$max_axis {
|
||||
return None;
|
||||
@ -6041,7 +6059,6 @@ impl World {
|
||||
let t_near = (min.$min_axis - from.$dir_axis) * inv_d;
|
||||
let t_far = (max.$max_axis - from.$dir_axis) * inv_d;
|
||||
|
||||
// Determine entry and exit points based on ray direction
|
||||
let (t_entry, t_exit, is_min_face) = if inv_d >= 0.0 {
|
||||
(t_near, t_far, true)
|
||||
} else {
|
||||
@ -6061,19 +6078,70 @@ impl World {
|
||||
}};
|
||||
}
|
||||
|
||||
check_axis!(x, x, x, x, BlockDirection::West, BlockDirection::East);
|
||||
check_axis!(y, y, y, y, BlockDirection::Down, BlockDirection::Up);
|
||||
check_axis!(z, z, z, z, BlockDirection::North, BlockDirection::South);
|
||||
check_axis!(x, x, x, x);
|
||||
check_axis!(y, y, y, y);
|
||||
check_axis!(z, z, z, z);
|
||||
|
||||
match (hit_axis, hit_is_min) {
|
||||
(Some("x"), true) => Some(BlockDirection::West),
|
||||
(Some("x"), false) => Some(BlockDirection::East),
|
||||
(Some("y"), true) => Some(BlockDirection::Down),
|
||||
(Some("y"), false) => Some(BlockDirection::Up),
|
||||
(Some("z"), true) => Some(BlockDirection::North),
|
||||
(Some("z"), false) => Some(BlockDirection::South),
|
||||
_ => None,
|
||||
if tmax < 0.0 || tmin > 1.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let direction = match (hit_axis, hit_is_min) {
|
||||
(Some("x"), true) => BlockDirection::West,
|
||||
(Some("x"), false) => BlockDirection::East,
|
||||
(Some("y"), true) => BlockDirection::Down,
|
||||
(Some("y"), false) => BlockDirection::Up,
|
||||
(Some("z"), true) => BlockDirection::North,
|
||||
(Some("z"), false) => BlockDirection::South,
|
||||
_ => {
|
||||
if dir.y < 0.0 {
|
||||
BlockDirection::Up
|
||||
} else if dir.y > 0.0 {
|
||||
BlockDirection::Down
|
||||
} else {
|
||||
BlockDirection::North
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let t_hit = tmin.max(0.0);
|
||||
let hit_pos = from + dir * t_hit;
|
||||
Some((t_hit, direction, hit_pos))
|
||||
}
|
||||
|
||||
pub fn ray_outline_check_detailed(
|
||||
&self,
|
||||
block_pos: &BlockPos,
|
||||
from: Vector3<f64>,
|
||||
to: Vector3<f64>,
|
||||
) -> Option<(BlockDirection, Vector3<f64>)> {
|
||||
let state = self.get_block_state(block_pos);
|
||||
|
||||
if state.outline_shapes.is_empty() {
|
||||
let block_min = block_pos.0.to_f64();
|
||||
let block_max = block_min.add_raw(1.0, 1.0, 1.0);
|
||||
return Self::intersects_aabb_with_hit(from, to, block_min, block_max)
|
||||
.map(|(_, dir, hit_pos)| (dir, hit_pos));
|
||||
}
|
||||
|
||||
let bounding_boxes = state.get_block_outline_shapes_at(block_pos);
|
||||
let mut closest_hit: Option<(f64, BlockDirection, Vector3<f64>)> = None;
|
||||
|
||||
for shape in bounding_boxes {
|
||||
let world_min = shape.min.add(&block_pos.0.to_f64());
|
||||
let world_max = shape.max.add(&block_pos.0.to_f64());
|
||||
|
||||
if let Some((t, dir, hit_pos)) =
|
||||
Self::intersects_aabb_with_hit(from, to, world_min, world_max)
|
||||
&& closest_hit
|
||||
.as_ref()
|
||||
.is_none_or(|(closest_t, _, _)| t < *closest_t)
|
||||
{
|
||||
closest_hit = Some((t, dir, hit_pos));
|
||||
}
|
||||
}
|
||||
|
||||
closest_hit.map(|(_, dir, hit_pos)| (dir, hit_pos))
|
||||
}
|
||||
|
||||
fn ray_outline_check(
|
||||
@ -6082,25 +6150,201 @@ impl World {
|
||||
from: Vector3<f64>,
|
||||
to: Vector3<f64>,
|
||||
) -> (bool, Option<BlockDirection>) {
|
||||
let state = self.get_block_state(block_pos);
|
||||
if let Some((dir, _)) = self.ray_outline_check_detailed(block_pos, from, to) {
|
||||
(true, Some(dir))
|
||||
} else {
|
||||
let state = self.get_block_state(block_pos);
|
||||
if state.outline_shapes.is_empty() {
|
||||
(true, None)
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.outline_shapes.is_empty() {
|
||||
return (true, None);
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn ray_trace_block(
|
||||
&self,
|
||||
start_pos: Vector3<f64>,
|
||||
end_pos: Vector3<f64>,
|
||||
include_fluids: bool,
|
||||
) -> Option<(BlockPos, BlockDirection, Vector3<f64>)> {
|
||||
if start_pos == end_pos {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bounding_boxes = state.get_block_outline_shapes_at(block_pos);
|
||||
let adjust = -1.0e-7f64;
|
||||
let to = end_pos.lerp(&start_pos, adjust);
|
||||
let from = start_pos.lerp(&end_pos, adjust);
|
||||
|
||||
for shape in bounding_boxes {
|
||||
let world_min = shape.min.add(&block_pos.0.to_f64());
|
||||
let world_max = shape.max.add(&block_pos.0.to_f64());
|
||||
let mut block = BlockPos::floored(from.x, from.y, from.z);
|
||||
|
||||
let direction = Self::intersects_aabb_with_direction(from, to, world_min, world_max);
|
||||
if direction.is_some() {
|
||||
return (true, direction);
|
||||
let state = self.get_block_state(&block);
|
||||
let valid_start = if include_fluids {
|
||||
!state.is_air()
|
||||
} else {
|
||||
!state.is_air() && !state.is_liquid()
|
||||
};
|
||||
if valid_start
|
||||
&& let Some((dir, hit_pos)) = self.ray_outline_check_detailed(&block, from, to)
|
||||
{
|
||||
return Some((block, dir, hit_pos));
|
||||
}
|
||||
|
||||
let difference = to.sub(&from);
|
||||
let step = difference.sign();
|
||||
|
||||
let delta = Vector3::new(
|
||||
if step.x == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
(f64::from(step.x)) / difference.x
|
||||
},
|
||||
if step.y == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
(f64::from(step.y)) / difference.y
|
||||
},
|
||||
if step.z == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
(f64::from(step.z)) / difference.z
|
||||
},
|
||||
);
|
||||
|
||||
let mut next = Vector3::new(
|
||||
delta.x
|
||||
* (if step.x > 0 {
|
||||
1.0 - (from.x - from.x.floor())
|
||||
} else {
|
||||
from.x - from.x.floor()
|
||||
}),
|
||||
delta.y
|
||||
* (if step.y > 0 {
|
||||
1.0 - (from.y - from.y.floor())
|
||||
} else {
|
||||
from.y - from.y.floor()
|
||||
}),
|
||||
delta.z
|
||||
* (if step.z > 0 {
|
||||
1.0 - (from.z - from.z.floor())
|
||||
} else {
|
||||
from.z - from.z.floor()
|
||||
}),
|
||||
);
|
||||
|
||||
while next.x <= 1.0 || next.y <= 1.0 || next.z <= 1.0 {
|
||||
let block_direction = match (next.x, next.y, next.z) {
|
||||
(x, y, z) if x < y && x < z => {
|
||||
block.0.x += step.x;
|
||||
next.x += delta.x;
|
||||
if step.x > 0 {
|
||||
BlockDirection::West
|
||||
} else {
|
||||
BlockDirection::East
|
||||
}
|
||||
}
|
||||
(_, y, z) if y < z => {
|
||||
block.0.y += step.y;
|
||||
next.y += delta.y;
|
||||
if step.y > 0 {
|
||||
BlockDirection::Down
|
||||
} else {
|
||||
BlockDirection::Up
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
block.0.z += step.z;
|
||||
next.z += delta.z;
|
||||
if step.z > 0 {
|
||||
BlockDirection::North
|
||||
} else {
|
||||
BlockDirection::South
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let state = self.get_block_state(&block);
|
||||
let hit = if include_fluids {
|
||||
!state.is_air()
|
||||
} else {
|
||||
!state.is_air() && !state.is_liquid()
|
||||
};
|
||||
|
||||
if hit {
|
||||
if let Some((dir, hit_pos)) = self.ray_outline_check_detailed(&block, from, to) {
|
||||
return Some((block, dir, hit_pos));
|
||||
}
|
||||
let block_min = block.0.to_f64();
|
||||
let block_max = block_min.add_raw(1.0, 1.0, 1.0);
|
||||
if let Some((_, dir, hit_pos)) =
|
||||
Self::intersects_aabb_with_hit(from, to, block_min, block_max)
|
||||
{
|
||||
return Some((block, dir, hit_pos));
|
||||
}
|
||||
return Some((block, block_direction, to));
|
||||
}
|
||||
}
|
||||
|
||||
(false, None)
|
||||
None
|
||||
}
|
||||
|
||||
pub fn ray_trace_entities(
|
||||
&self,
|
||||
start: Vector3<f64>,
|
||||
end: Vector3<f64>,
|
||||
) -> Vec<(Arc<dyn EntityBase>, Vector3<f64>, f64)> {
|
||||
if start == end {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let min_x = start.x.min(end.x) - 1.0;
|
||||
let max_x = start.x.max(end.x) + 1.0;
|
||||
let min_y = start.y.min(end.y) - 1.0;
|
||||
let max_y = start.y.max(end.y) + 1.0;
|
||||
let min_z = start.z.min(end.z) - 1.0;
|
||||
let max_z = start.z.max(end.z) + 1.0;
|
||||
let ray_box = BoundingBox::new(
|
||||
Vector3::new(min_x, min_y, min_z),
|
||||
Vector3::new(max_x, max_y, max_z),
|
||||
);
|
||||
|
||||
let mut hits = Vec::new();
|
||||
|
||||
for entity in self.entities.load().iter() {
|
||||
let bb = entity.get_entity().bounding_box.load();
|
||||
if bb.intersects(&ray_box)
|
||||
&& let Some((t, _, hit_pos)) =
|
||||
Self::intersects_aabb_with_hit(start, end, bb.min, bb.max)
|
||||
{
|
||||
let distance = (hit_pos - start).length();
|
||||
hits.push((entity.clone(), hit_pos, distance, t));
|
||||
}
|
||||
}
|
||||
|
||||
for player in self.players.load().iter() {
|
||||
let bb = player.get_entity().bounding_box.load();
|
||||
if bb.intersects(&ray_box)
|
||||
&& let Some((t, _, hit_pos)) =
|
||||
Self::intersects_aabb_with_hit(start, end, bb.min, bb.max)
|
||||
{
|
||||
let distance = (hit_pos - start).length();
|
||||
hits.push((player.clone() as Arc<dyn EntityBase>, hit_pos, distance, t));
|
||||
}
|
||||
}
|
||||
|
||||
hits.sort_by(|a, b| a.3.partial_cmp(&b.3).unwrap_or(std::cmp::Ordering::Equal));
|
||||
hits.into_iter()
|
||||
.map(|(ent, hit_pos, dist, _)| (ent, hit_pos, dist))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn ray_trace_entity(
|
||||
&self,
|
||||
start: Vector3<f64>,
|
||||
end: Vector3<f64>,
|
||||
) -> Option<(Arc<dyn EntityBase>, Vector3<f64>, f64)> {
|
||||
self.ray_trace_entities(start, end).into_iter().next()
|
||||
}
|
||||
|
||||
pub async fn raycast(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user