feat: add more items
This commit is contained in:
parent
b18c26ccb4
commit
6720ac265e
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@ -10,16 +10,16 @@ crates/pumpkin/src/net/ @Snowiiii @xRookieFight
|
||||
|
||||
# Bedrock related changes
|
||||
assets/bedrock/ @ZlordHUN @xRookieFight
|
||||
crates/pumpkin-protocol/src/bedrock/ @ZlordHUN @xRookieFight
|
||||
crates/pumpkin/src/net/bedrock/ @ZlordHUN @xRookieFight
|
||||
crates/pumpkin-protocol/src/bedrock/ @ZlordHUN @xRookieFight @QuantumSegfault
|
||||
crates/pumpkin/src/net/bedrock/ @ZlordHUN @xRookieFight @QuantumSegfault
|
||||
|
||||
# Command related changes
|
||||
crates/pumpkin/src/command/ @aure31
|
||||
|
||||
# Plugin related changes & WASM Host
|
||||
crates/pumpkin-plugin-api/ @vyPal @yunuservices @QuantumSegfault
|
||||
crates/pumpkin-plugin-api/ @vyPal @yunuservices
|
||||
crates/pumpkin-plugin-wit/ @vyPal @yunuservices @QuantumSegfault
|
||||
crates/pumpkin-api-macros/ @vyPal @yunuservices @QuantumSegfault
|
||||
crates/pumpkin-api-macros/ @vyPal @yunuservices
|
||||
crates/pumpkin/src/plugin/ @vyPal @yunuservices @QuantumSegfault
|
||||
|
||||
# Documentation related changes
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::data_component_impl::DataComponentImpl;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
@ -11,35 +12,79 @@ impl WritableBookContentImpl {
|
||||
if let NbtTag::Compound(c) = tag
|
||||
&& let Some(NbtTag::List(l)) = c.get("pages")
|
||||
{
|
||||
for _ in l {
|
||||
pages.push(String::new());
|
||||
for item in l {
|
||||
if let NbtTag::String(s) = item {
|
||||
pages.push(s.to_string());
|
||||
} else if let NbtTag::Compound(comp) = item {
|
||||
if let Some(s) = comp.get_string("raw") {
|
||||
pages.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Self { pages })
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for WritableBookContentImpl {
|
||||
fn write_data(&self) -> NbtTag {
|
||||
let mut compound = NbtCompound::new();
|
||||
let pages_tags: Vec<NbtTag> = self
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| NbtTag::String(p.clone().into_boxed_str()))
|
||||
.collect();
|
||||
compound.put("pages", NbtTag::List(pages_tags));
|
||||
NbtTag::Compound(compound)
|
||||
}
|
||||
default_impl!(WritableBookContent);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct WrittenBookContentImpl {
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub pages: Vec<String>,
|
||||
}
|
||||
impl WrittenBookContentImpl {
|
||||
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||
let mut pages = Vec::new();
|
||||
if let NbtTag::Compound(c) = tag
|
||||
&& let Some(NbtTag::List(l)) = c.get("pages")
|
||||
{
|
||||
for _ in l {
|
||||
pages.push(String::new());
|
||||
let mut title = String::new();
|
||||
let mut author = String::new();
|
||||
if let NbtTag::Compound(c) = tag {
|
||||
if let Some(s) = c.get_string("title") {
|
||||
title = s.to_string();
|
||||
}
|
||||
if let Some(s) = c.get_string("author") {
|
||||
author = s.to_string();
|
||||
}
|
||||
if let Some(NbtTag::List(l)) = c.get("pages") {
|
||||
for item in l {
|
||||
if let NbtTag::String(s) = item {
|
||||
pages.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Self { pages })
|
||||
Some(Self {
|
||||
title,
|
||||
author,
|
||||
pages,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for WrittenBookContentImpl {
|
||||
fn write_data(&self) -> NbtTag {
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_string("title", self.title.clone());
|
||||
compound.put_string("author", self.author.clone());
|
||||
let pages_tags: Vec<NbtTag> = self
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| NbtTag::String(p.clone().into_boxed_str()))
|
||||
.collect();
|
||||
compound.put("pages", NbtTag::List(pages_tags));
|
||||
NbtTag::Compound(compound)
|
||||
}
|
||||
default_impl!(WrittenBookContent);
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ mod login;
|
||||
mod map_item_data;
|
||||
mod merchant_offers;
|
||||
mod multi_block_update;
|
||||
mod open_book;
|
||||
mod open_screen;
|
||||
mod open_sign_editor;
|
||||
mod particle;
|
||||
@ -152,6 +153,7 @@ pub use login::*;
|
||||
pub use map_item_data::*;
|
||||
pub use merchant_offers::*;
|
||||
pub use multi_block_update::*;
|
||||
pub use open_book::*;
|
||||
pub use open_screen::*;
|
||||
pub use open_sign_editor::*;
|
||||
pub use particle::*;
|
||||
@ -219,5 +221,8 @@ pub use update_objectives::*;
|
||||
pub use update_score::*;
|
||||
pub use worldevent::*;
|
||||
|
||||
mod waypoint;
|
||||
pub use waypoint::*;
|
||||
|
||||
mod debug_sample;
|
||||
pub use debug_sample::*;
|
||||
|
||||
30
crates/pumpkin-protocol/src/java/client/play/open_book.rs
Normal file
30
crates/pumpkin-protocol/src/java/client/play/open_book.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use pumpkin_data::packet::clientbound::PLAY_OPEN_BOOK;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
use crate::ser::NetworkWriteExt;
|
||||
use crate::{ClientPacket, VarInt};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[java_packet(PLAY_OPEN_BOOK)]
|
||||
pub struct COpenBook {
|
||||
pub hand: VarInt,
|
||||
}
|
||||
|
||||
impl COpenBook {
|
||||
#[must_use]
|
||||
pub const fn new(hand: VarInt) -> Self {
|
||||
Self { hand }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for COpenBook {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl std::io::Write,
|
||||
_version: &JavaMinecraftVersion,
|
||||
) -> Result<(), crate::ser::WritingError> {
|
||||
write.write_var_int(&self.hand)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
167
crates/pumpkin-protocol/src/java/client/play/waypoint.rs
Normal file
167
crates/pumpkin-protocol/src/java/client/play/waypoint.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crate::ser::NetworkWriteExt;
|
||||
use crate::{ClientPacket, VarInt, WritingError};
|
||||
use pumpkin_data::packet::clientbound::PLAY_WAYPOINT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum WaypointOperation {
|
||||
Track = 0,
|
||||
Untrack = 1,
|
||||
Update = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WaypointTarget {
|
||||
Position(BlockPos),
|
||||
Chunk { x: i32, z: i32 },
|
||||
Azimuth(f32),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WaypointIcon<'a> {
|
||||
pub style: Option<&'a str>,
|
||||
pub color: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrackedWaypoint<'a> {
|
||||
pub identifier: Uuid,
|
||||
pub icon: Option<WaypointIcon<'a>>,
|
||||
pub target: WaypointTarget,
|
||||
}
|
||||
|
||||
impl TrackedWaypoint<'_> {
|
||||
#[must_use]
|
||||
pub const fn empty(identifier: Uuid) -> Self {
|
||||
Self {
|
||||
identifier,
|
||||
icon: None,
|
||||
target: WaypointTarget::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn set_position(
|
||||
identifier: Uuid,
|
||||
icon: Option<WaypointIcon<'_>>,
|
||||
position: BlockPos,
|
||||
) -> TrackedWaypoint<'_> {
|
||||
TrackedWaypoint {
|
||||
identifier,
|
||||
icon,
|
||||
target: WaypointTarget::Position(position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Syncs tracked waypoints (`ClientboundTrackedWaypointPacket`) to client.
|
||||
#[java_packet(PLAY_WAYPOINT)]
|
||||
pub struct CWaypoint<'a> {
|
||||
pub operation: WaypointOperation,
|
||||
pub waypoint: TrackedWaypoint<'a>,
|
||||
}
|
||||
|
||||
impl<'a> CWaypoint<'a> {
|
||||
#[must_use]
|
||||
pub const fn new(operation: WaypointOperation, waypoint: TrackedWaypoint<'a>) -> Self {
|
||||
Self {
|
||||
operation,
|
||||
waypoint,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn remove(identifier: Uuid) -> Self {
|
||||
Self {
|
||||
operation: WaypointOperation::Untrack,
|
||||
waypoint: TrackedWaypoint::empty(identifier),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn add_position(
|
||||
identifier: Uuid,
|
||||
icon: Option<WaypointIcon<'a>>,
|
||||
position: BlockPos,
|
||||
) -> Self {
|
||||
Self {
|
||||
operation: WaypointOperation::Track,
|
||||
waypoint: TrackedWaypoint::set_position(identifier, icon, position),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn update_position(
|
||||
identifier: Uuid,
|
||||
icon: Option<WaypointIcon<'a>>,
|
||||
position: BlockPos,
|
||||
) -> Self {
|
||||
Self {
|
||||
operation: WaypointOperation::Update,
|
||||
waypoint: TrackedWaypoint::set_position(identifier, icon, position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CWaypoint<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl Write,
|
||||
_version: &JavaMinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
// 1. Operation (TRACK = 0, UNTRACK = 1, UPDATE = 2)
|
||||
write.write_var_int(&VarInt(self.operation as i32))?;
|
||||
|
||||
// 2. TrackedWaypoint Identifier (UUID)
|
||||
write.write_uuid(&self.waypoint.identifier)?;
|
||||
|
||||
// 3. Waypoint Icon (Optional)
|
||||
if let Some(ref icon) = self.waypoint.icon {
|
||||
write.write_bool(true)?;
|
||||
if let Some(style_str) = icon.style {
|
||||
write.write_bool(true)?;
|
||||
// Format style as a valid ResourceLocation (e.g., "minecraft:red" or "red")
|
||||
if style_str.contains(':') {
|
||||
write.write_string(style_str)?;
|
||||
} else {
|
||||
let formatted = format!("minecraft:{style_str}");
|
||||
write.write_string(&formatted)?;
|
||||
}
|
||||
} else {
|
||||
write.write_bool(false)?;
|
||||
}
|
||||
write.write_i32_be(icon.color)?;
|
||||
} else {
|
||||
write.write_bool(false)?;
|
||||
}
|
||||
|
||||
// 4. Waypoint Target Payload
|
||||
match &self.waypoint.target {
|
||||
WaypointTarget::Position(pos) => {
|
||||
write.write_var_int(&VarInt(0))?;
|
||||
write.write_block_pos(pos)?;
|
||||
}
|
||||
WaypointTarget::Chunk { x, z } => {
|
||||
write.write_var_int(&VarInt(1))?;
|
||||
write.write_i32_be(*x)?;
|
||||
write.write_i32_be(*z)?;
|
||||
}
|
||||
WaypointTarget::Azimuth(angle) => {
|
||||
write.write_var_int(&VarInt(2))?;
|
||||
write.write_f32_be(*angle)?;
|
||||
}
|
||||
WaypointTarget::Empty => {
|
||||
write.write_var_int(&VarInt(3))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
35
crates/pumpkin-protocol/src/java/server/play/edit_book.rs
Normal file
35
crates/pumpkin-protocol/src/java/server/play/edit_book.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use pumpkin_data::packet::serverbound::PLAY_EDIT_BOOK;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
use crate::{
|
||||
ServerPacket, VarInt,
|
||||
ser::{NetworkReadExt, NetworkReadSliceExt, ReadingError},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[java_packet(PLAY_EDIT_BOOK)]
|
||||
pub struct SEditBook<'a> {
|
||||
pub slot: VarInt,
|
||||
pub pages: Vec<&'a str>,
|
||||
pub title: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> ServerPacket<'a> for SEditBook<'a> {
|
||||
fn read(read: &mut &'a [u8], _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
|
||||
let slot = read.get_var_int()?;
|
||||
let count = read.get_var_int()?.0 as usize;
|
||||
let count = count.min(100);
|
||||
let mut pages = Vec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
pages.push(read.get_str_bounded_borrowed(1024)?);
|
||||
}
|
||||
let has_title = read.get_bool()?;
|
||||
let title = if has_title {
|
||||
Some(read.get_str_bounded_borrowed(128)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self { slot, pages, title })
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ mod container_button_click;
|
||||
mod cookie_response;
|
||||
mod custom_click_action;
|
||||
mod custom_payload;
|
||||
mod edit_book;
|
||||
mod interact;
|
||||
mod jigsaw_generate;
|
||||
mod keep_alive;
|
||||
@ -66,6 +67,7 @@ pub use container_button_click::*;
|
||||
pub use cookie_response::*;
|
||||
pub use custom_click_action::*;
|
||||
pub use custom_payload::*;
|
||||
pub use edit_book::*;
|
||||
pub use interact::*;
|
||||
pub use jigsaw_generate::*;
|
||||
pub use keep_alive::*;
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs};
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs, UseWithItemArgs,
|
||||
};
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockId;
|
||||
use pumpkin_data::block_properties::{BlockProperties, WaterCauldronLikeProperties};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
pub struct CauldronBlock;
|
||||
|
||||
@ -17,6 +25,178 @@ impl BlockMetadata for CauldronBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CauldronBlock {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item_id = args.item_stack.item.id;
|
||||
let block_id = args.block.id;
|
||||
let gamemode = args.player.gamemode.load();
|
||||
|
||||
// Filling empty cauldron with buckets
|
||||
if block_id == BlockId::CAULDRON {
|
||||
if item_id == Item::WATER_BUCKET.id {
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::LAVA_BUCKET.id {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::LAVA_CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyLava,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POWDER_SNOW_BUCKET.id {
|
||||
let state_id = Block::POWDER_SNOW_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::POWDER_SNOW_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyPowderSnow,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POTION.id {
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "1")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(
|
||||
ItemStack::new(1, &Item::GLASS_BOTTLE),
|
||||
args.player.as_ref(),
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// Collecting fluid from full cauldrons into empty bucket
|
||||
if item_id == Item::BUCKET.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let (filled_item, sound) = if block_id == BlockId::WATER_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 3 {
|
||||
(Some(&Item::WATER_BUCKET), Sound::ItemBucketFill)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFill)
|
||||
}
|
||||
} else if block_id == BlockId::LAVA_CAULDRON {
|
||||
(Some(&Item::LAVA_BUCKET), Sound::ItemBucketFillLava)
|
||||
} else if block_id == BlockId::POWDER_SNOW_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 3 {
|
||||
(
|
||||
Some(&Item::POWDER_SNOW_BUCKET),
|
||||
Sound::ItemBucketFillPowderSnow,
|
||||
)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFillPowderSnow)
|
||||
}
|
||||
} else {
|
||||
(None, Sound::ItemBucketFill)
|
||||
};
|
||||
|
||||
if let Some(result_item) = filled_item {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world
|
||||
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, result_item), args.player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding water bottle to non-full water cauldron
|
||||
if block_id == BlockId::WATER_CAULDRON && item_id == Item::POTION.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level < 3 {
|
||||
let next_level_str = match props.level {
|
||||
1 => "2",
|
||||
_ => "3",
|
||||
};
|
||||
let new_state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", next_level_str)])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(
|
||||
ItemStack::new(1, &Item::GLASS_BOTTLE),
|
||||
args.player.as_ref(),
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
})
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
|
||||
@ -227,7 +227,7 @@ impl BlockBehaviour for LecternBlock {
|
||||
Box::pin(async move {
|
||||
let item_stack = &mut *args.item_stack;
|
||||
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
|
||||
return BlockActionResult::Pass;
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let props = LecternLikeProperties::from_state_id(
|
||||
@ -236,14 +236,14 @@ impl BlockBehaviour for LecternBlock {
|
||||
);
|
||||
if props.has_book {
|
||||
// Fall through so `normal_use` opens the reading screen.
|
||||
return BlockActionResult::Pass;
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let Some(lectern) = args.world.get_block_entity(args.position) else {
|
||||
return BlockActionResult::Pass;
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
let Some(lectern) = lectern.as_any().downcast_ref::<LecternBlockEntity>() else {
|
||||
return BlockActionResult::Pass;
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_protocol::java::client::play::{CWaypoint, WaypointIcon};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::CommandResult;
|
||||
use crate::command::args::{
|
||||
FindArg, entity::EntityArgumentConsumer, hex_color::HexColorArgumentConsumer,
|
||||
resource_location::ResourceLocationArgumentConsumer, team_color::TeamColorArgumentConsumer,
|
||||
};
|
||||
use crate::command::tree::builder::{argument, literal};
|
||||
use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree};
|
||||
use crate::command::{
|
||||
CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree,
|
||||
};
|
||||
|
||||
const NAMES: [&str; 1] = ["waypoint"];
|
||||
const DESCRIPTION: &str = "List or modify waypoints.";
|
||||
@ -31,7 +34,6 @@ impl CommandExecutor for ListExecutor {
|
||||
.expect("There should always be at least one world");
|
||||
let dimension = world.dimension.minecraft_name.to_string();
|
||||
|
||||
// Currently no active waypoints are tracked in the level
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
translation::java::COMMANDS_WAYPOINT_LIST_EMPTY,
|
||||
@ -59,7 +61,38 @@ impl CommandExecutor for ColorExecutor {
|
||||
args: &'a ConsumedArgs<'a>,
|
||||
) -> CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let _waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||
let waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||
let entity = waypoint_entity.get_entity();
|
||||
let pos = entity.pos.load();
|
||||
let block_pos = BlockPos::new(
|
||||
pos.x.floor() as i32,
|
||||
pos.y.floor() as i32,
|
||||
pos.z.floor() as i32,
|
||||
);
|
||||
let uuid = entity.entity_uuid;
|
||||
|
||||
let color_val = match self.0 {
|
||||
ColorAction::Named => {
|
||||
let color = TeamColorArgumentConsumer::find_arg(args, ARG_COLOR)?;
|
||||
let rgb = color.to_rgb();
|
||||
i32::from_be_bytes([0, rgb.red, rgb.green, rgb.blue])
|
||||
}
|
||||
ColorAction::Hex => HexColorArgumentConsumer::find_arg(args, ARG_COLOR)? as i32,
|
||||
ColorAction::Reset => 0xFFFFFF,
|
||||
};
|
||||
|
||||
let packet = CWaypoint::update_position(
|
||||
uuid,
|
||||
Some(WaypointIcon {
|
||||
style: None,
|
||||
color: color_val,
|
||||
}),
|
||||
block_pos,
|
||||
);
|
||||
|
||||
if let Some(player) = sender.as_player() {
|
||||
player.client.enqueue_packet(&packet).await;
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
ColorAction::Named => {
|
||||
@ -111,13 +144,35 @@ impl CommandExecutor for StyleExecutor {
|
||||
args: &'a ConsumedArgs<'a>,
|
||||
) -> CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let _waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||
let waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||
let entity = waypoint_entity.get_entity();
|
||||
let pos = entity.pos.load();
|
||||
let block_pos = BlockPos::new(
|
||||
pos.x.floor() as i32,
|
||||
pos.y.floor() as i32,
|
||||
pos.z.floor() as i32,
|
||||
);
|
||||
let uuid = entity.entity_uuid;
|
||||
|
||||
match self.0 {
|
||||
let style_str = match self.0 {
|
||||
StyleAction::Set => {
|
||||
let _style = ResourceLocationArgumentConsumer::find_arg(args, ARG_STYLE)?;
|
||||
let style = ResourceLocationArgumentConsumer::find_arg(args, ARG_STYLE)?;
|
||||
Some(style)
|
||||
}
|
||||
StyleAction::Reset => {}
|
||||
StyleAction::Reset => None,
|
||||
};
|
||||
|
||||
let packet = CWaypoint::update_position(
|
||||
uuid,
|
||||
Some(WaypointIcon {
|
||||
style: style_str,
|
||||
color: 0xFFFFFF,
|
||||
}),
|
||||
block_pos,
|
||||
);
|
||||
|
||||
if let Some(player) = sender.as_player() {
|
||||
player.client.enqueue_packet(&packet).await;
|
||||
}
|
||||
|
||||
sender
|
||||
|
||||
57
crates/pumpkin/src/entity/item_steerable.rs
Normal file
57
crates/pumpkin/src/entity/item_steerable.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use std::any::Any;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
|
||||
pub trait ItemSteerable: Send + Sync {
|
||||
/// Attempts to boost speed. Returns `true` if boost was successfully activated.
|
||||
fn boost(&self) -> bool;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ItemBasedSteering {
|
||||
pub boosting: AtomicBool,
|
||||
pub boost_time: AtomicI32,
|
||||
pub boost_time_total: AtomicI32,
|
||||
}
|
||||
|
||||
impl ItemBasedSteering {
|
||||
pub const MIN_BOOST_TIME: i32 = 140;
|
||||
pub const MAX_BOOST_TIME: i32 = 700;
|
||||
|
||||
#[must_use]
|
||||
pub fn boost(&self) -> bool {
|
||||
if self.boosting.load(Ordering::Relaxed) {
|
||||
false
|
||||
} else {
|
||||
self.boosting.store(true, Ordering::Relaxed);
|
||||
self.boost_time.store(0, Ordering::Relaxed);
|
||||
let random_range = Self::MAX_BOOST_TIME - Self::MIN_BOOST_TIME + 1;
|
||||
let random_time =
|
||||
(rand::random::<u32>() as i32).abs() % random_range + Self::MIN_BOOST_TIME;
|
||||
self.boost_time_total.store(random_time, Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick_boost(&self) {
|
||||
if self.boosting.load(Ordering::Relaxed) {
|
||||
let current = self.boost_time.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
let total = self.boost_time_total.load(Ordering::Relaxed);
|
||||
if current >= total {
|
||||
self.boosting.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn boost_factor(&self) -> f32 {
|
||||
if self.boosting.load(Ordering::Relaxed) {
|
||||
let current = self.boost_time.load(Ordering::Relaxed) as f32;
|
||||
let total = self.boost_time_total.load(Ordering::Relaxed).max(1) as f32;
|
||||
1.0 + 1.15 * (current / total * std::f32::consts::PI).sin()
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2825,31 +2825,57 @@ impl LivingEntity {
|
||||
};
|
||||
|
||||
for consume_effect in consumable.effects.iter() {
|
||||
let ConsumeEffect::ApplyEffects((effects, probability)) = consume_effect else {
|
||||
continue;
|
||||
};
|
||||
if !consume_effect_probability_applies(*probability, rand::random()) {
|
||||
continue;
|
||||
}
|
||||
match consume_effect {
|
||||
ConsumeEffect::ApplyEffects((effects, probability)) => {
|
||||
if !consume_effect_probability_applies(*probability, rand::random()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for effect in effects.iter() {
|
||||
let Some(effect_type) = StatusEffect::from_minecraft_name(&effect.effect_id) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(amplifier) = u8::try_from(effect.amplifier) else {
|
||||
continue;
|
||||
};
|
||||
for effect in effects.iter() {
|
||||
let Some(effect_type) =
|
||||
StatusEffect::from_minecraft_name(&effect.effect_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(amplifier) = u8::try_from(effect.amplifier) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.add_effect(Effect {
|
||||
effect_type,
|
||||
duration: effect.duration,
|
||||
amplifier,
|
||||
ambient: effect.ambient,
|
||||
show_particles: effect.show_particles,
|
||||
show_icon: effect.show_icon,
|
||||
blend: false,
|
||||
})
|
||||
.await;
|
||||
self.add_effect(Effect {
|
||||
effect_type,
|
||||
duration: effect.duration,
|
||||
amplifier,
|
||||
ambient: effect.ambient,
|
||||
show_particles: effect.show_particles,
|
||||
show_icon: effect.show_icon,
|
||||
blend: false,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
ConsumeEffect::ClearAllEffects => {
|
||||
self.reset_effects_and_attributes().await;
|
||||
}
|
||||
ConsumeEffect::RemoveEffects(idset) => {
|
||||
if let pumpkin_data::data_component_impl::IDSet::IDs(ids) = idset {
|
||||
for effect_type in ids.iter() {
|
||||
self.remove_effect(effect_type).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConsumeEffect::TeleportRandomly(diameter) => {
|
||||
let center = self.entity.pos.load();
|
||||
let radius = f64::from(*diameter) / 2.0;
|
||||
let target_x = center.x + (rand::random::<f64>() - 0.5) * radius * 2.0;
|
||||
let target_y = center.y + (rand::random::<f64>() - 0.5) * radius;
|
||||
let target_z = center.z + (rand::random::<f64>() - 0.5) * radius * 2.0;
|
||||
let pos =
|
||||
pumpkin_util::math::vector3::Vector3::new(target_x, target_y, target_z);
|
||||
let (yaw, pitch) = (self.entity.yaw.load(), self.entity.pitch.load());
|
||||
let world = self.entity.world.load().clone();
|
||||
self.entity.teleport(pos, Some(yaw), Some(pitch), world);
|
||||
}
|
||||
ConsumeEffect::PlaySound(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,6 +508,20 @@ pub trait Mob: EntityBase + Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_item_steerable(&self) -> Option<&dyn crate::entity::item_steerable::ItemSteerable> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_saddled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn can_be_saddled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_saddled(&self, _saddled: bool) {}
|
||||
|
||||
/// Per-mob tick hook called each tick before AI runs. Override for mob-specific logic.
|
||||
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) -> EntityBaseFuture<'a, ()> {
|
||||
Box::pin(async {})
|
||||
@ -605,12 +619,20 @@ pub trait Mob: EntityBase + Send + Sync {
|
||||
}
|
||||
|
||||
fn mob_set_variant_name(&self, _name: &str) {}
|
||||
|
||||
fn get_sheep(&self) -> Option<&crate::entity::passive::sheep::SheepEntity> {
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<T: Mob + Send + 'static> EntityBase for T {
|
||||
fn get_mob(&self) -> Option<&dyn Mob> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn get_item_steerable(&self) -> Option<&dyn crate::entity::item_steerable::ItemSteerable> {
|
||||
Mob::get_item_steerable(self)
|
||||
}
|
||||
|
||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.mob_init_data_tracker().await;
|
||||
|
||||
@ -92,6 +92,7 @@ pub mod experience_orb;
|
||||
pub mod falling;
|
||||
pub mod hunger;
|
||||
pub mod item;
|
||||
pub mod item_steerable;
|
||||
pub mod living;
|
||||
pub mod mob;
|
||||
pub mod passive;
|
||||
@ -165,6 +166,10 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_item_steerable(&self) -> Option<&dyn crate::entity::item_steerable::ItemSteerable> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_eye_pos(&self) -> Vector3<f64> {
|
||||
self.get_entity().get_eye_pos()
|
||||
}
|
||||
|
||||
@ -25,12 +25,17 @@ const PIG_FOOD: &[&Item] = &[
|
||||
&Item::CARROT_ON_A_STICK,
|
||||
];
|
||||
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::item_steerable::{ItemBasedSteering, ItemSteerable};
|
||||
|
||||
/// Represents a Pig, a common passive mob that provides porkchops.
|
||||
///
|
||||
/// Wiki: <https://minecraft.wiki/w/Pig>
|
||||
pub struct PigEntity {
|
||||
pub mob_entity: MobEntity,
|
||||
pub ageable_data: crate::entity::ageable::AgeableData,
|
||||
pub steering: ItemBasedSteering,
|
||||
pub saddled: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl PigEntity {
|
||||
@ -39,6 +44,8 @@ impl PigEntity {
|
||||
let pig = Self {
|
||||
mob_entity,
|
||||
ageable_data: crate::entity::ageable::AgeableData::default(),
|
||||
steering: ItemBasedSteering::default(),
|
||||
saddled: std::sync::atomic::AtomicBool::new(false),
|
||||
};
|
||||
let mob_arc = Arc::new(pig);
|
||||
let mob_weak: Weak<dyn Mob> = {
|
||||
@ -82,6 +89,7 @@ impl NBTStorage for PigEntity {
|
||||
self.mob_entity.living_entity.write_nbt(nbt).await;
|
||||
self.write_ageable_nbt(nbt);
|
||||
self.write_animal_nbt(nbt);
|
||||
nbt.put_bool("Saddle", self.is_saddled());
|
||||
})
|
||||
}
|
||||
|
||||
@ -90,6 +98,9 @@ impl NBTStorage for PigEntity {
|
||||
self.mob_entity.living_entity.read_nbt_non_mut(nbt).await;
|
||||
self.read_ageable_nbt(nbt);
|
||||
self.read_animal_nbt(nbt);
|
||||
if let Some(saddle) = nbt.get_byte("Saddle") {
|
||||
self.set_saddled(saddle == 1);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -105,12 +116,54 @@ impl Mob for PigEntity {
|
||||
&self.mob_entity
|
||||
}
|
||||
|
||||
fn get_item_steerable(&self) -> Option<&dyn ItemSteerable> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn is_saddled(&self) -> bool {
|
||||
self.saddled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn can_be_saddled(&self) -> bool {
|
||||
use crate::entity::ageable::AgeableMob;
|
||||
self.mob_entity.living_entity.entity.is_alive() && !self.is_baby()
|
||||
}
|
||||
|
||||
fn set_saddled(&self, saddled: bool) {
|
||||
self.saddled
|
||||
.store(saddled, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn mob_interact<'a>(
|
||||
&'a self,
|
||||
player: &'a Arc<Player>,
|
||||
item_stack: &'a mut ItemStack,
|
||||
) -> EntityBaseFuture<'a, bool> {
|
||||
use super::animal::Animal;
|
||||
self.animal_interact(player, item_stack, Sound::EntityPigAmbient)
|
||||
Box::pin(async move {
|
||||
if self.is_saddled() && !self.is_food(item_stack) {
|
||||
let world = player.world();
|
||||
if let Some(vehicle) = world.get_entity_by_id(self.get_entity().entity_id)
|
||||
&& let Some(passenger) = world.get_player_by_id(player.entity_id())
|
||||
{
|
||||
self.get_entity()
|
||||
.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self.animal_interact(player, item_stack, Sound::EntityPigAmbient)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemSteerable for PigEntity {
|
||||
fn boost(&self) -> bool {
|
||||
self.steering.boost()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +148,10 @@ impl Mob for SheepEntity {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_sheep(&self) -> Option<&SheepEntity> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn mob_interact<'a>(
|
||||
&'a self,
|
||||
player: &'a Arc<Player>,
|
||||
|
||||
@ -11,14 +11,22 @@ use crate::entity::{
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
|
||||
use crate::entity::item_steerable::{ItemBasedSteering, ItemSteerable};
|
||||
|
||||
pub struct StriderEntity {
|
||||
pub mob_entity: MobEntity,
|
||||
pub steering: ItemBasedSteering,
|
||||
pub saddled: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl StriderEntity {
|
||||
pub fn new(entity: Entity) -> Arc<Self> {
|
||||
let mob_entity = MobEntity::new(entity);
|
||||
let strider = Self { mob_entity };
|
||||
let strider = Self {
|
||||
mob_entity,
|
||||
steering: ItemBasedSteering::default(),
|
||||
saddled: std::sync::atomic::AtomicBool::new(false),
|
||||
};
|
||||
let mob_arc = Arc::new(strider);
|
||||
let mob_weak: Weak<dyn Mob> = {
|
||||
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
||||
@ -45,10 +53,82 @@ impl StriderEntity {
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for StriderEntity {}
|
||||
use crate::entity::EntityBase;
|
||||
|
||||
impl NBTStorage for StriderEntity {
|
||||
fn write_nbt<'a>(
|
||||
&'a self,
|
||||
nbt: &'a mut pumpkin_nbt::compound::NbtCompound,
|
||||
) -> crate::entity::NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.mob_entity.living_entity.write_nbt(nbt).await;
|
||||
nbt.put_bool("Saddle", self.is_saddled());
|
||||
})
|
||||
}
|
||||
|
||||
fn read_nbt_non_mut<'a>(
|
||||
&'a self,
|
||||
nbt: &'a pumpkin_nbt::compound::NbtCompound,
|
||||
) -> crate::entity::NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.mob_entity.living_entity.read_nbt_non_mut(nbt).await;
|
||||
if let Some(saddle) = nbt.get_byte("Saddle") {
|
||||
self.set_saddled(saddle == 1);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for StriderEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.mob_entity
|
||||
}
|
||||
|
||||
fn get_item_steerable(&self) -> Option<&dyn ItemSteerable> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn is_saddled(&self) -> bool {
|
||||
self.saddled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn can_be_saddled(&self) -> bool {
|
||||
self.mob_entity.living_entity.entity.is_alive()
|
||||
}
|
||||
|
||||
fn set_saddled(&self, saddled: bool) {
|
||||
self.saddled
|
||||
.store(saddled, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn mob_interact<'a>(
|
||||
&'a self,
|
||||
player: &'a Arc<crate::entity::player::Player>,
|
||||
_item_stack: &'a mut pumpkin_data::item_stack::ItemStack,
|
||||
) -> crate::entity::EntityBaseFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
if self.is_saddled() {
|
||||
let world = player.world();
|
||||
let ent = &self.mob_entity.living_entity.entity;
|
||||
if let Some(vehicle) = world.get_entity_by_id(ent.entity_id)
|
||||
&& let Some(passenger) = world.get_player_by_id(player.entity_id())
|
||||
{
|
||||
ent.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemSteerable for StriderEntity {
|
||||
fn boost(&self) -> bool {
|
||||
self.steering.boost()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
172
crates/pumpkin/src/item/items/bone_meal.rs
Normal file
172
crates/pumpkin/src/item/items/bone_meal.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
pub struct BoneMealItem;
|
||||
|
||||
impl ItemMetadata for BoneMealItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::BONE_MEAL.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for BoneMealItem {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
_face: BlockDirection,
|
||||
_cursor_pos: Vector3<f32>,
|
||||
block: &'a Block,
|
||||
_server: &'a Server,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
let state_id = world.get_block_state_id(&location);
|
||||
|
||||
// Compute crop age progression without holding Box<dyn BlockProperties> across await
|
||||
let crop_action = block.properties(state_id).and_then(|props| {
|
||||
let prop_map = props.to_props();
|
||||
prop_map
|
||||
.iter()
|
||||
.find(|(k, _)| *k == "age")
|
||||
.and_then(|(_, age_val)| age_val.parse::<u8>().ok())
|
||||
.and_then(|current_age| {
|
||||
let max_age = match block.id {
|
||||
id if id == Block::BEETROOTS.id || id == Block::SWEET_BERRY_BUSH.id => {
|
||||
3
|
||||
}
|
||||
id if id == Block::TORCHFLOWER_CROP.id => 1,
|
||||
id if id == Block::PITCHER_CROP.id => 4,
|
||||
_ => 7,
|
||||
};
|
||||
(current_age < max_age).then(|| {
|
||||
let bonus = (rand::random::<u32>() % 4 + 2) as u8;
|
||||
let new_age = (current_age + bonus).min(max_age).to_string();
|
||||
let new_props: Vec<(&str, &str)> = prop_map
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if *k == "age" {
|
||||
(*k, new_age.as_str())
|
||||
} else {
|
||||
(*k, *v)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
block.from_properties(&new_props).to_state_id(block)
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(new_state_id) = crop_action {
|
||||
world
|
||||
.set_block_state(&location, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.play_sound(
|
||||
Sound::ItemBoneMealUse,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute sapling stage progression without holding Box<dyn BlockProperties> across await
|
||||
let sapling_action = block.properties(state_id).and_then(|props| {
|
||||
let prop_map = props.to_props();
|
||||
prop_map
|
||||
.iter()
|
||||
.find(|(k, _)| *k == "stage")
|
||||
.and_then(|(_, stage_val)| stage_val.parse::<u8>().ok())
|
||||
.filter(|&stage| stage < 1)
|
||||
.map(|_| {
|
||||
let new_props: Vec<(&str, &str)> = prop_map
|
||||
.iter()
|
||||
.map(|(k, v)| if *k == "stage" { (*k, "1") } else { (*k, *v) })
|
||||
.collect();
|
||||
block.from_properties(&new_props).to_state_id(block)
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(new_state_id) = sapling_action {
|
||||
world
|
||||
.set_block_state(&location, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.play_sound(
|
||||
Sound::ItemBoneMealUse,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Grass Block / Moss Block bone-mealing
|
||||
if block.id == Block::GRASS_BLOCK.id || block.id == Block::MOSS_BLOCK.id {
|
||||
let center_x = location.0.x;
|
||||
let center_y = location.0.y;
|
||||
let center_z = location.0.z;
|
||||
|
||||
for dx in -2..=2 {
|
||||
for dz in -2..=2 {
|
||||
if dx == 0 && dz == 0 {
|
||||
continue;
|
||||
}
|
||||
if rand::random::<u32>().is_multiple_of(2) {
|
||||
let target_pos =
|
||||
BlockPos::new(center_x + dx, center_y + 1, center_z + dz);
|
||||
let target_below =
|
||||
BlockPos::new(center_x + dx, center_y, center_z + dz);
|
||||
let (below_block, _) = world.get_block_and_state_id(&target_below);
|
||||
let (target_block, _) = world.get_block_and_state_id(&target_pos);
|
||||
|
||||
if (below_block.id == Block::GRASS_BLOCK.id
|
||||
|| below_block.id == Block::MOSS_BLOCK.id)
|
||||
&& target_block.id == Block::AIR.id
|
||||
{
|
||||
let plant_block = if rand::random::<u32>().is_multiple_of(8) {
|
||||
Block::DANDELION.default_state.id
|
||||
} else if (rand::random::<u32>() + 1).is_multiple_of(8) {
|
||||
Block::POPPY.default_state.id
|
||||
} else {
|
||||
Block::SHORT_GRASS.default_state.id
|
||||
};
|
||||
world
|
||||
.set_block_state(
|
||||
&target_pos,
|
||||
plant_block,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world.play_sound(
|
||||
Sound::ItemBoneMealUse,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
240
crates/pumpkin/src/item/items/brush.rs
Normal file
240
crates/pumpkin/src/item/items/brush.rs
Normal file
@ -0,0 +1,240 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::{Entity, EntityBase};
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockStateId};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
pub struct BrushItem;
|
||||
|
||||
impl ItemMetadata for BrushItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::BRUSH.id])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_dusted_stage(block: &Block, state_id: BlockStateId) -> u8 {
|
||||
block
|
||||
.properties(state_id)
|
||||
.and_then(|props| {
|
||||
props
|
||||
.to_props()
|
||||
.into_iter()
|
||||
.find(|(key, _)| *key == "dusted")
|
||||
.and_then(|(_, val)| val.parse::<u8>().ok())
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn set_dusted_stage(block: &Block, state_id: BlockStateId, stage: u8) -> BlockStateId {
|
||||
let Some(props) = block.properties(state_id) else {
|
||||
return state_id;
|
||||
};
|
||||
let stage_str = stage.to_string();
|
||||
let original_props = props.to_props();
|
||||
let updated_props: Vec<(&str, &str)> = original_props
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
if *key == "dusted" {
|
||||
("dusted", stage_str.as_str())
|
||||
} else {
|
||||
(*key, *value)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
block.from_properties(&updated_props).to_state_id(block)
|
||||
}
|
||||
|
||||
fn get_random_archaeology_loot() -> &'static Item {
|
||||
let loot_table = [
|
||||
&Item::ARCHER_POTTERY_SHERD,
|
||||
&Item::PRIZE_POTTERY_SHERD,
|
||||
&Item::SKULL_POTTERY_SHERD,
|
||||
&Item::ARMS_UP_POTTERY_SHERD,
|
||||
&Item::ANGLER_POTTERY_SHERD,
|
||||
&Item::EMERALD,
|
||||
&Item::DIAMOND,
|
||||
&Item::SNIFFER_EGG,
|
||||
&Item::BONE,
|
||||
&Item::GOLD_NUGGET,
|
||||
&Item::COAL,
|
||||
];
|
||||
let idx = (rand::random::<u32>() as usize) % loot_table.len();
|
||||
loot_table[idx]
|
||||
}
|
||||
|
||||
impl ItemBehaviour for BrushItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
player.world().play_sound(
|
||||
Sound::ItemBrushBrushingGeneric,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, Self::USE_DURATION)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
_item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
_face: BlockDirection,
|
||||
_cursor_pos: Vector3<f32>,
|
||||
block: &'a Block,
|
||||
_server: &'a Server,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
let is_sand = block == &Block::SUSPICIOUS_SAND;
|
||||
let is_gravel = block == &Block::SUSPICIOUS_GRAVEL;
|
||||
let block_center = Vector3::new(
|
||||
f64::from(location.0.x) + 0.5,
|
||||
f64::from(location.0.y) + 0.5,
|
||||
f64::from(location.0.z) + 0.5,
|
||||
);
|
||||
|
||||
if is_sand || is_gravel {
|
||||
let current_state_id = world.get_block_state_id(&location);
|
||||
let current_stage = get_dusted_stage(block, current_state_id);
|
||||
|
||||
if current_stage < 3 {
|
||||
let next_stage_id =
|
||||
set_dusted_stage(block, current_state_id, current_stage + 1);
|
||||
world
|
||||
.set_block_state(&location, next_stage_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
|
||||
world.play_sound(
|
||||
if is_sand {
|
||||
Sound::ItemBrushBrushingSand
|
||||
} else {
|
||||
Sound::ItemBrushBrushingGravel
|
||||
},
|
||||
SoundCategory::Blocks,
|
||||
&block_center,
|
||||
);
|
||||
} else {
|
||||
let replacement_state_id = if is_sand {
|
||||
Block::SAND.default_state.id
|
||||
} else {
|
||||
Block::GRAVEL.default_state.id
|
||||
};
|
||||
|
||||
world
|
||||
.set_block_state(&location, replacement_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
|
||||
world.play_sound(
|
||||
if is_sand {
|
||||
Sound::ItemBrushBrushingSandComplete
|
||||
} else {
|
||||
Sound::ItemBrushBrushingGravelComplete
|
||||
},
|
||||
SoundCategory::Blocks,
|
||||
&block_center,
|
||||
);
|
||||
|
||||
let loot_item = get_random_archaeology_loot();
|
||||
let spawn_pos = Vector3::new(
|
||||
f64::from(location.0.x) + 0.5,
|
||||
f64::from(location.0.y) + 1.0,
|
||||
f64::from(location.0.z) + 0.5,
|
||||
);
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), spawn_pos, &EntityType::ITEM),
|
||||
ItemStack::new(1, loot_item),
|
||||
));
|
||||
world.spawn_entity(item_entity).await;
|
||||
}
|
||||
|
||||
player.damage_held_item(1).await;
|
||||
} else {
|
||||
world.play_sound(
|
||||
Sound::ItemBrushBrushingGeneric,
|
||||
SoundCategory::Blocks,
|
||||
&block_center,
|
||||
);
|
||||
}
|
||||
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, Self::USE_DURATION)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn use_on_entity<'a>(
|
||||
&'a self,
|
||||
_item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
entity: Arc<dyn EntityBase>,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let ent = entity.get_entity();
|
||||
if ent.entity_type == &EntityType::ARMADILLO {
|
||||
let world = player.world();
|
||||
world.play_sound(
|
||||
Sound::EntityArmadilloBrush,
|
||||
SoundCategory::Neutral,
|
||||
&ent.pos.load(),
|
||||
);
|
||||
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), ent.pos.load(), &EntityType::ITEM),
|
||||
ItemStack::new(1, &Item::ARMADILLO_SCUTE),
|
||||
));
|
||||
world.spawn_entity(item_entity).await;
|
||||
|
||||
player.damage_held_item(16).await;
|
||||
} else {
|
||||
let world = player.world();
|
||||
world.play_sound(
|
||||
Sound::ItemBrushBrushingGeneric,
|
||||
SoundCategory::Neutral,
|
||||
&ent.pos.load(),
|
||||
);
|
||||
}
|
||||
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, Self::USE_DURATION)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_use_duration(&self) -> i32 {
|
||||
Self::USE_DURATION
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BrushItem {
|
||||
pub const USE_DURATION: i32 = 200;
|
||||
}
|
||||
@ -22,6 +22,7 @@ use crate::world::World;
|
||||
|
||||
pub struct EmptyBucketItem;
|
||||
pub struct FilledBucketItem;
|
||||
pub struct MilkBucketItem;
|
||||
|
||||
impl ItemMetadata for EmptyBucketItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
@ -46,11 +47,11 @@ impl ItemMetadata for FilledBucketItem {
|
||||
}
|
||||
}
|
||||
|
||||
// impl ItemMetadata for MilkBucketItem {
|
||||
// fn ids() -> Box<[u16]> {
|
||||
// [Item::MILK_BUCKET.id].into()
|
||||
// }
|
||||
// }
|
||||
impl ItemMetadata for MilkBucketItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[Item::MILK_BUCKET.id].into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_start_and_end_pos(player: &Player) -> (Vector3<f64>, Vector3<f64>) {
|
||||
let start_pos = player.eye_position();
|
||||
@ -399,4 +400,37 @@ impl ItemBehaviour for FilledBucketItem {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Implement MilkBucketItem
|
||||
impl ItemBehaviour for MilkBucketItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, 32)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn on_stopped_using<'a>(
|
||||
&'a self,
|
||||
_stack: &'a ItemStack,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
player.living_entity.reset_effects_and_attributes().await;
|
||||
give_player_bucket_item(player, &Item::BUCKET).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_use_duration(&self) -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
37
crates/pumpkin/src/item/items/clock.rs
Normal file
37
crates/pumpkin/src/item/items/clock.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct ClockItem;
|
||||
|
||||
impl ItemMetadata for ClockItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::CLOCK.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for ClockItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
world.play_sound(
|
||||
Sound::UiButtonClick,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
71
crates/pumpkin/src/item/items/compass.rs
Normal file
71
crates/pumpkin/src/item/items/compass.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{DataComponentImpl, LodestoneTarget, LodestoneTrackerImpl};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
pub struct CompassItem;
|
||||
|
||||
impl ItemMetadata for CompassItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::COMPASS.id, Item::RECOVERY_COMPASS.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for CompassItem {
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
_face: BlockDirection,
|
||||
_cursor_pos: Vector3<f32>,
|
||||
block: &'a Block,
|
||||
_server: &'a Server,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if block.id == Block::LODESTONE.id && item.item.id == Item::COMPASS.id {
|
||||
let world = player.world();
|
||||
world.play_sound(
|
||||
Sound::ItemLodestoneCompassLock,
|
||||
SoundCategory::Players,
|
||||
&location.to_f64(),
|
||||
);
|
||||
|
||||
let mut lodestone_compass = ItemStack::new(1, &Item::COMPASS);
|
||||
let tracker = LodestoneTrackerImpl {
|
||||
target: Some(LodestoneTarget {
|
||||
dimension: world.dimension.minecraft_name.to_string(),
|
||||
x: location.0.x,
|
||||
y: location.0.y,
|
||||
z: location.0.z,
|
||||
}),
|
||||
tracked: true,
|
||||
};
|
||||
lodestone_compass
|
||||
.patch
|
||||
.push((DataComponent::LodestoneTracker, Some(tracker.to_dyn())));
|
||||
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(lodestone_compass, player)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
46
crates/pumpkin/src/item/items/experience_bottle.rs
Normal file
46
crates/pumpkin/src/item/items/experience_bottle.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::experience_orb::ExperienceOrbEntity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct ExperienceBottleItem;
|
||||
|
||||
impl ItemMetadata for ExperienceBottleItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::EXPERIENCE_BOTTLE.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for ExperienceBottleItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
let pos = player.eye_position();
|
||||
world.play_sound(
|
||||
Sound::EntityExperienceBottleThrow,
|
||||
SoundCategory::Players,
|
||||
&pos,
|
||||
);
|
||||
|
||||
let amount = (rand::random::<u32>() % 9 + 3) as u32; // 3..=11 exp
|
||||
ExperienceOrbEntity::spawn(&world, pos, amount).await;
|
||||
|
||||
let mut held = player.inventory().held_item().await;
|
||||
held.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player.inventory().set_held_item(held).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
107
crates/pumpkin/src/item/items/glass_bottle.rs
Normal file
107
crates/pumpkin/src/item/items/glass_bottle.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
pub struct GlassBottleItem;
|
||||
|
||||
impl ItemMetadata for GlassBottleItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::GLASS_BOTTLE.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for GlassBottleItem {
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
face: BlockDirection,
|
||||
_cursor_pos: Vector3<f32>,
|
||||
block: &'a Block,
|
||||
_server: &'a Server,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
|
||||
let is_water_target =
|
||||
block.id == Block::WATER.id || block.id == Block::WATER_CAULDRON.id;
|
||||
|
||||
let check_pos = if is_water_target {
|
||||
location
|
||||
} else {
|
||||
location.offset(face.to_offset())
|
||||
};
|
||||
|
||||
let (check_block, check_state_id) = world.get_block_and_state_id(&check_pos);
|
||||
|
||||
if check_block.id == Block::WATER.id || check_block.id == Block::WATER_CAULDRON.id {
|
||||
let cauldron_action = (check_block.id == Block::WATER_CAULDRON.id)
|
||||
.then(|| {
|
||||
check_block.properties(check_state_id).and_then(|props| {
|
||||
let prop_map = props.to_props();
|
||||
prop_map
|
||||
.iter()
|
||||
.find(|(k, _)| *k == "level")
|
||||
.and_then(|(_, level_str)| level_str.parse::<u8>().ok())
|
||||
.map(|level| {
|
||||
if level > 1 {
|
||||
let new_level = (level - 1).to_string();
|
||||
let new_props: Vec<(&str, &str)> = prop_map
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if *k == "level" {
|
||||
(*k, new_level.as_str())
|
||||
} else {
|
||||
(*k, *v)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
check_block
|
||||
.from_properties(&new_props)
|
||||
.to_state_id(check_block)
|
||||
} else {
|
||||
Block::CAULDRON.default_state.id
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
|
||||
if let Some(new_state_id) = cauldron_action {
|
||||
world
|
||||
.set_block_state(&check_pos, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
}
|
||||
|
||||
world.play_sound(
|
||||
Sound::ItemBottleFill,
|
||||
SoundCategory::Players,
|
||||
&check_pos.to_f64(),
|
||||
);
|
||||
|
||||
let water_bottle = ItemStack::new(1, &Item::POTION);
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(water_bottle, player)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
49
crates/pumpkin/src/item/items/goat_horn.rs
Normal file
49
crates/pumpkin/src/item/items/goat_horn.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct GoatHornItem;
|
||||
|
||||
impl ItemMetadata for GoatHornItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::GOAT_HORN.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for GoatHornItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
player.world().play_sound(
|
||||
Sound::ItemGoatHornSound0,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, Self::USE_DURATION)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_use_duration(&self) -> i32 {
|
||||
Self::USE_DURATION
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl GoatHornItem {
|
||||
pub const USE_DURATION: i32 = 140;
|
||||
}
|
||||
@ -2,47 +2,70 @@ pub mod armor_stand;
|
||||
pub mod arrow;
|
||||
pub mod axe;
|
||||
pub mod boat;
|
||||
pub mod bone_meal;
|
||||
pub mod bow;
|
||||
pub mod brush;
|
||||
pub mod bucket;
|
||||
pub mod bundle;
|
||||
pub mod clock;
|
||||
pub mod compass;
|
||||
pub mod crossbow;
|
||||
pub mod dye;
|
||||
pub mod egg;
|
||||
pub mod end_crystal;
|
||||
pub mod ender_eye;
|
||||
pub mod ender_pearl;
|
||||
pub mod experience_bottle;
|
||||
pub mod firework_rocket;
|
||||
pub mod fishing_rod;
|
||||
pub mod glass_bottle;
|
||||
pub mod glowing_ink_sac;
|
||||
pub mod goat_horn;
|
||||
pub mod hoe;
|
||||
pub mod honeycomb;
|
||||
pub mod ignite;
|
||||
pub mod ink_sac;
|
||||
pub mod lead;
|
||||
pub mod mace;
|
||||
pub mod map;
|
||||
pub mod minecart;
|
||||
pub mod name_tag;
|
||||
pub mod on_a_stick;
|
||||
pub mod potions;
|
||||
pub mod saddle;
|
||||
pub mod shears;
|
||||
pub mod shovel;
|
||||
pub mod snowball;
|
||||
pub mod spawn_egg;
|
||||
pub mod spyglass;
|
||||
pub mod swords;
|
||||
pub mod trident;
|
||||
pub mod wind_charge;
|
||||
|
||||
pub mod lead;
|
||||
pub mod writable_book;
|
||||
|
||||
use crate::item::items::armor_stand::ArmorStandItem;
|
||||
use crate::item::items::boat::BoatItem;
|
||||
use crate::item::items::bone_meal::BoneMealItem;
|
||||
use crate::item::items::brush::BrushItem;
|
||||
use crate::item::items::bundle::BundleItem;
|
||||
use crate::item::items::clock::ClockItem;
|
||||
use crate::item::items::compass::CompassItem;
|
||||
use crate::item::items::end_crystal::EndCrystalItem;
|
||||
use crate::item::items::experience_bottle::ExperienceBottleItem;
|
||||
use crate::item::items::glass_bottle::GlassBottleItem;
|
||||
use crate::item::items::goat_horn::GoatHornItem;
|
||||
use crate::item::items::lead::LeadItem;
|
||||
use crate::item::items::map::MapItem;
|
||||
|
||||
use crate::item::items::minecart::MinecartItem;
|
||||
use crate::item::items::name_tag::NameTagItem;
|
||||
use crate::item::items::on_a_stick::{CarrotOnAStickItem, WarpedFungusOnAStickItem};
|
||||
use crate::item::items::saddle::SaddleItem;
|
||||
use crate::item::items::shears::ShearsItem;
|
||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||
use crate::item::items::spyglass::SpyglassItem;
|
||||
use crate::item::items::wind_charge::WindChargeItem;
|
||||
use crate::item::items::writable_book::WritableBookItem;
|
||||
use firework_rocket::FireworkRocketItem;
|
||||
use fishing_rod::FishingRodItem;
|
||||
use glowing_ink_sac::GlowingInkSacItem;
|
||||
@ -52,7 +75,7 @@ use crate::item::items::potions::{LingeringPotionItem, PotionItem, SplashPotionI
|
||||
use arrow::ArrowItem;
|
||||
use axe::AxeItem;
|
||||
use bow::BowItem;
|
||||
use bucket::{EmptyBucketItem, FilledBucketItem};
|
||||
use bucket::{EmptyBucketItem, FilledBucketItem, MilkBucketItem};
|
||||
use crossbow::CrossbowItem;
|
||||
use dye::DyeItem;
|
||||
use egg::EggItem;
|
||||
@ -85,11 +108,22 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(MaceItem);
|
||||
manager.register(TridentItem);
|
||||
manager.register(FishingRodItem);
|
||||
// TODO: Register CrossbowItem with per-shot durability cost.
|
||||
// TODO: Register BrushItem with per-stroke durability cost.
|
||||
// TODO: Register CarrotOnAStickItem and WarpedFungusOnAStickItem with boost durability costs.
|
||||
manager.register(BrushItem);
|
||||
manager.register(CarrotOnAStickItem);
|
||||
manager.register(WarpedFungusOnAStickItem);
|
||||
manager.register(ShearsItem);
|
||||
manager.register(SaddleItem);
|
||||
manager.register(BoneMealItem);
|
||||
manager.register(GlassBottleItem);
|
||||
manager.register(ExperienceBottleItem);
|
||||
manager.register(CompassItem);
|
||||
manager.register(ClockItem);
|
||||
manager.register(WritableBookItem);
|
||||
manager.register(SpyglassItem);
|
||||
manager.register(GoatHornItem);
|
||||
manager.register(EmptyBucketItem);
|
||||
manager.register(FilledBucketItem);
|
||||
manager.register(MilkBucketItem);
|
||||
manager.register(ShovelItem);
|
||||
manager.register(SpawnEggItem);
|
||||
manager.register(AxeItem);
|
||||
|
||||
67
crates/pumpkin/src/item/items/on_a_stick.rs
Normal file
67
crates/pumpkin/src/item/items/on_a_stick.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
|
||||
pub struct CarrotOnAStickItem;
|
||||
pub struct WarpedFungusOnAStickItem;
|
||||
|
||||
impl ItemMetadata for CarrotOnAStickItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::CARROT_ON_A_STICK.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for CarrotOnAStickItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let vehicle_opt = player.get_entity().vehicle.lock().await.clone();
|
||||
if let Some(vehicle) = vehicle_opt
|
||||
&& let Some(steerable) = vehicle.get_item_steerable()
|
||||
&& steerable.boost()
|
||||
{
|
||||
player.damage_held_item(7).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemMetadata for WarpedFungusOnAStickItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::WARPED_FUNGUS_ON_A_STICK.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for WarpedFungusOnAStickItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let vehicle_opt = player.get_entity().vehicle.lock().await.clone();
|
||||
if let Some(vehicle) = vehicle_opt
|
||||
&& let Some(steerable) = vehicle.get_item_steerable()
|
||||
&& steerable.boost()
|
||||
{
|
||||
player.damage_held_item(7).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
51
crates/pumpkin/src/item/items/saddle.rs
Normal file
51
crates/pumpkin/src/item/items/saddle.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct SaddleItem;
|
||||
|
||||
impl ItemMetadata for SaddleItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::SADDLE.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for SaddleItem {
|
||||
fn use_on_entity<'a>(
|
||||
&'a self,
|
||||
item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
entity: Arc<dyn EntityBase>,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if let Some(mob) = entity.get_mob()
|
||||
&& mob.can_be_saddled()
|
||||
&& !mob.is_saddled()
|
||||
{
|
||||
mob.set_saddled(true);
|
||||
let ent = entity.get_entity();
|
||||
let sound = if ent.entity_type == &pumpkin_data::entity::EntityType::STRIDER {
|
||||
Sound::EntityStriderSaddle
|
||||
} else {
|
||||
Sound::EntityPigSaddle
|
||||
};
|
||||
player
|
||||
.world()
|
||||
.play_sound(sound, SoundCategory::Neutral, &ent.pos.load());
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
246
crates/pumpkin/src/item/items/shears.rs
Normal file
246
crates/pumpkin/src/item/items/shears.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockStateId};
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
pub struct ShearsItem;
|
||||
|
||||
impl ItemMetadata for ShearsItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::SHEARS.id])
|
||||
}
|
||||
}
|
||||
|
||||
const fn get_wool_item_for_color(color: u8) -> &'static Item {
|
||||
match color {
|
||||
0 => &Item::WHITE_WOOL,
|
||||
1 => &Item::ORANGE_WOOL,
|
||||
2 => &Item::MAGENTA_WOOL,
|
||||
3 => &Item::LIGHT_BLUE_WOOL,
|
||||
4 => &Item::YELLOW_WOOL,
|
||||
5 => &Item::LIME_WOOL,
|
||||
6 => &Item::PINK_WOOL,
|
||||
7 => &Item::GRAY_WOOL,
|
||||
8 => &Item::LIGHT_GRAY_WOOL,
|
||||
9 => &Item::CYAN_WOOL,
|
||||
10 => &Item::PURPLE_WOOL,
|
||||
11 => &Item::BLUE_WOOL,
|
||||
12 => &Item::BROWN_WOOL,
|
||||
13 => &Item::GREEN_WOOL,
|
||||
14 => &Item::RED_WOOL,
|
||||
_ => &Item::BLACK_WOOL,
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for ShearsItem {
|
||||
fn can_mine(&self, player: &Player) -> bool {
|
||||
player.gamemode.load() != GameMode::Creative
|
||||
}
|
||||
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
_item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
_face: BlockDirection,
|
||||
_cursor_pos: Vector3<f32>,
|
||||
block: &'a Block,
|
||||
_server: &'a Server,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
let state_id = world.get_block_state_id(&location);
|
||||
|
||||
if handle_growing_plant(player, &location, block, state_id).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if handle_beehive(player, &location, block, state_id).await {
|
||||
return;
|
||||
}
|
||||
|
||||
handle_pumpkin(player, &location, block).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn use_on_entity<'a>(
|
||||
&'a self,
|
||||
_item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
entity: Arc<dyn EntityBase>,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if let Some(sheep) = entity.get_mob().and_then(|m| m.get_sheep())
|
||||
&& !sheep.is_sheared()
|
||||
{
|
||||
sheep.set_sheared(true);
|
||||
let world = player.world();
|
||||
let pos = sheep.mob_entity.living_entity.entity.pos.load();
|
||||
world.play_sound(Sound::EntitySheepShear, SoundCategory::Players, &pos);
|
||||
|
||||
let wool_count = (rand::random::<u8>() % 3 + 1) as u8;
|
||||
let wool_item = get_wool_item_for_color(sheep.get_color());
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), pos, &EntityType::ITEM),
|
||||
ItemStack::new(wool_count, wool_item),
|
||||
));
|
||||
world.spawn_entity(item_entity).await;
|
||||
player.damage_held_item(1).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_growing_plant(
|
||||
player: &Player,
|
||||
location: &BlockPos,
|
||||
block: &Block,
|
||||
state_id: BlockStateId,
|
||||
) -> bool {
|
||||
let is_growing_plant = block.id == Block::KELP.id
|
||||
|| block.id == Block::CAVE_VINES.id
|
||||
|| block.id == Block::CAVE_VINES_PLANT.id
|
||||
|| block.id == Block::TWISTING_VINES.id
|
||||
|| block.id == Block::WEEPING_VINES.id;
|
||||
|
||||
if !is_growing_plant {
|
||||
return false;
|
||||
}
|
||||
|
||||
let world = player.world();
|
||||
let action = block.properties(state_id).and_then(|props| {
|
||||
let prop_map = props.to_props();
|
||||
prop_map
|
||||
.iter()
|
||||
.find(|(k, _)| *k == "age")
|
||||
.and_then(|(_, age_str)| age_str.parse::<u8>().ok())
|
||||
.filter(|&age| age < 25)
|
||||
.map(|_| {
|
||||
let new_props: Vec<(&str, &str)> = prop_map
|
||||
.iter()
|
||||
.map(|(k, v)| if *k == "age" { (*k, "25") } else { (*k, *v) })
|
||||
.collect();
|
||||
block.from_properties(&new_props).to_state_id(block)
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(new_state_id) = action {
|
||||
world
|
||||
.set_block_state(location, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.play_sound(
|
||||
Sound::BlockGrowingPlantCrop,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
player.damage_held_item(1).await;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
async fn handle_beehive(
|
||||
player: &Player,
|
||||
location: &BlockPos,
|
||||
block: &Block,
|
||||
state_id: BlockStateId,
|
||||
) -> bool {
|
||||
if block.id != Block::BEEHIVE.id && block.id != Block::BEE_NEST.id {
|
||||
return false;
|
||||
}
|
||||
|
||||
let world = player.world();
|
||||
let action = block.properties(state_id).and_then(|props| {
|
||||
let prop_map = props.to_props();
|
||||
prop_map
|
||||
.iter()
|
||||
.find(|(k, v)| *k == "honey_level" && *v == "5")
|
||||
.map(|_| {
|
||||
let new_props: Vec<(&str, &str)> = prop_map
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if *k == "honey_level" {
|
||||
(*k, "0")
|
||||
} else {
|
||||
(*k, *v)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
block.from_properties(&new_props).to_state_id(block)
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(new_state_id) = action {
|
||||
world
|
||||
.set_block_state(location, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.play_sound(
|
||||
Sound::BlockBeehiveShear,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
|
||||
let drop_pos = Vector3::new(
|
||||
f64::from(location.0.x) + 0.5,
|
||||
f64::from(location.0.y) + 0.5,
|
||||
f64::from(location.0.z) + 0.5,
|
||||
);
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), drop_pos, &EntityType::ITEM),
|
||||
ItemStack::new(3, &Item::HONEYCOMB),
|
||||
));
|
||||
world.spawn_entity(item_entity).await;
|
||||
player.damage_held_item(1).await;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
async fn handle_pumpkin(player: &Player, location: &BlockPos, block: &Block) {
|
||||
if block.id == Block::PUMPKIN.id {
|
||||
let world = player.world();
|
||||
let carved_state = Block::CARVED_PUMPKIN.default_state.id;
|
||||
world
|
||||
.set_block_state(location, carved_state, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.play_sound(
|
||||
Sound::BlockPumpkinCarve,
|
||||
SoundCategory::Blocks,
|
||||
&location.to_f64(),
|
||||
);
|
||||
|
||||
let drop_pos = Vector3::new(
|
||||
f64::from(location.0.x) + 0.5,
|
||||
f64::from(location.0.y) + 0.5,
|
||||
f64::from(location.0.z) + 0.5,
|
||||
);
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), drop_pos, &EntityType::ITEM),
|
||||
ItemStack::new(4, &Item::PUMPKIN_SEEDS),
|
||||
));
|
||||
world.spawn_entity(item_entity).await;
|
||||
player.damage_held_item(1).await;
|
||||
}
|
||||
}
|
||||
63
crates/pumpkin/src/item/items/spyglass.rs
Normal file
63
crates/pumpkin/src/item/items/spyglass.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct SpyglassItem;
|
||||
|
||||
impl ItemMetadata for SpyglassItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::SPYGLASS.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for SpyglassItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
player.world().play_sound(
|
||||
Sound::ItemSpyglassUse,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
let stack = player.inventory().held_item().await;
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(pumpkin_util::Hand::Right, stack, Self::USE_DURATION)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn on_stopped_using<'a>(
|
||||
&'a self,
|
||||
_stack: &'a pumpkin_data::item_stack::ItemStack,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
player.world().play_sound(
|
||||
Sound::ItemSpyglassStopUsing,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn get_use_duration(&self) -> i32 {
|
||||
Self::USE_DURATION
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SpyglassItem {
|
||||
pub const USE_DURATION: i32 = 1200;
|
||||
}
|
||||
44
crates/pumpkin/src/item/items/writable_book.rs
Normal file
44
crates/pumpkin/src/item/items/writable_book.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::java::client::play::COpenBook;
|
||||
|
||||
pub struct WritableBookItem;
|
||||
|
||||
impl ItemMetadata for WritableBookItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::WRITABLE_BOOK.id, Item::WRITTEN_BOOK.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for WritableBookItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if item.id == Item::WRITTEN_BOOK.id {
|
||||
player
|
||||
.client
|
||||
.enqueue_packet(&COpenBook::new(VarInt(0))) // 0 = main hand
|
||||
.await;
|
||||
player.world().play_sound(
|
||||
Sound::ItemBookPageTurn,
|
||||
SoundCategory::Players,
|
||||
&player.position(),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ use pumpkin_protocol::java::server::play::{
|
||||
SClickSlot, SClientCommand, SClientInformationPlay, SClientTickEnd, SCloseContainer,
|
||||
SCommandSuggestion, SConfirmTeleport, SContainerButtonClick,
|
||||
SCookieResponse as SPCookieResponse, SCustomPayload, SDebugSampleSubscription,
|
||||
SDebugSubscriptionRequest, SInteract, SJigsawGenerate, SMoveVehicle, SPaddleBoat,
|
||||
SDebugSubscriptionRequest, SEditBook, SInteract, SJigsawGenerate, SMoveVehicle, SPaddleBoat,
|
||||
SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest, SPlayerAbilities, SPlayerAction,
|
||||
SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation,
|
||||
SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SRecipeBookSeenRecipe, SRenameItem,
|
||||
@ -895,6 +895,10 @@ impl JavaClient {
|
||||
self.handle_sign_update(player, SUpdateSign::read(&mut payload, &version)?)
|
||||
.await;
|
||||
}
|
||||
id if id == SEditBook::to_id(version) => {
|
||||
self.handle_edit_book(player, SEditBook::read(&mut payload, &version)?)
|
||||
.await;
|
||||
}
|
||||
id if id == SUseItemOn::to_id(version) => {
|
||||
self.handle_use_item_on(player, SUseItemOn::read(&mut payload, &version)?, server)
|
||||
.await?;
|
||||
|
||||
@ -40,8 +40,10 @@ use crate::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent;
|
||||
use crate::server::{Server, seasonal_events};
|
||||
use crate::world::{BlockBreakingProgress, World, chunker};
|
||||
use pumpkin_data::block_properties::{BlockProperties, CommandBlockLikeProperties};
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{
|
||||
BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl,
|
||||
BlocksAttacksImpl, ConsumableImpl, DataComponentImpl, EquipmentSlot, EquippableImpl, FoodImpl,
|
||||
WritableBookContentImpl, WrittenBookContentImpl,
|
||||
};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
@ -64,13 +66,14 @@ use pumpkin_protocol::java::server::play::{
|
||||
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected,
|
||||
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClientCommand,
|
||||
SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
|
||||
SCookieResponse as SPCookieResponse, SInteract, SJigsawGenerate, SKeepAlive, SMoveVehicle,
|
||||
SPaddleBoat, SPickItemFromBlock, SPickItemFromEntity, SPlaceRecipe, SPlayPingRequest,
|
||||
SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerPosition,
|
||||
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings,
|
||||
SRecipeBookSeenRecipe, SSeenAdvancement, SSelectTrade, SSetCommandBlock, SSetCreativeSlot,
|
||||
SSetHeldItem, SSetJigsawBlock, SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity,
|
||||
STestInstanceBlockAction, SUpdateSign, SUseItem, SUseItemOn, Status,
|
||||
SCookieResponse as SPCookieResponse, SEditBook, SInteract, SJigsawGenerate, SKeepAlive,
|
||||
SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPickItemFromEntity, SPlaceRecipe,
|
||||
SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerInput,
|
||||
SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SPlayerSession,
|
||||
SRecipeBookChangeSettings, SRecipeBookSeenRecipe, SSeenAdvancement, SSelectTrade,
|
||||
SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetJigsawBlock, SSetPlayerGround,
|
||||
SSetTestBlock, SSwingArm, STeleportToEntity, STestInstanceBlockAction, SUpdateSign, SUseItem,
|
||||
SUseItemOn, Status,
|
||||
};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::math::{polynomial_rolling_hash, position::BlockPos, wrap_degrees};
|
||||
@ -2511,6 +2514,38 @@ impl JavaClient {
|
||||
world.update_block_entity(&block_entity);
|
||||
}
|
||||
|
||||
pub async fn handle_edit_book(&self, player: &Player, packet: SEditBook<'_>) {
|
||||
let held_stack = player.inventory().held_item().await;
|
||||
if held_stack.item.id != Item::WRITABLE_BOOK.id {
|
||||
return;
|
||||
}
|
||||
|
||||
let pages: Vec<String> = packet.pages.iter().map(|p| (*p).to_string()).collect();
|
||||
|
||||
if let Some(title) = packet.title {
|
||||
let mut written_book = ItemStack::new(1, &Item::WRITTEN_BOOK);
|
||||
let content = WrittenBookContentImpl {
|
||||
title: title.to_string(),
|
||||
author: player.gameprofile.name.clone(),
|
||||
pages,
|
||||
};
|
||||
written_book
|
||||
.patch
|
||||
.push((DataComponent::WrittenBookContent, Some(content.to_dyn())));
|
||||
player.inventory().set_held_item(written_book).await;
|
||||
} else {
|
||||
let mut writable_book = held_stack;
|
||||
let content = WritableBookContentImpl { pages };
|
||||
writable_book
|
||||
.patch
|
||||
.retain(|(component, _)| *component != DataComponent::WritableBookContent);
|
||||
writable_book
|
||||
.patch
|
||||
.push((DataComponent::WritableBookContent, Some(content.to_dyn())));
|
||||
player.inventory().set_held_item(writable_book).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_use_item(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user