mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add buckets with fluids (#736)
* Buckets start * buckets in survival * lint * fmt * lint * fix clippy allow * fix creative inventory for buckets * fmt, lint * remove logs * fix placing lava * lint * fix issues * fmt * bruh * Fix comments * code quality
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use bytes::BufMut;
|
||||
use std::ops::{Add, AddAssign, Div, Mul, Sub};
|
||||
|
||||
use num_traits::Float;
|
||||
use num_traits::{Float, Num};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
|
||||
pub struct Vector3<T> {
|
||||
@@ -10,7 +10,7 @@ pub struct Vector3<T> {
|
||||
pub z: T,
|
||||
}
|
||||
|
||||
impl<T: Math + Copy> Vector3<T> {
|
||||
impl<T: Math + PartialOrd + Copy> Vector3<T> {
|
||||
pub const fn new(x: T, y: T, z: T) -> Self {
|
||||
Vector3 { x, y, z }
|
||||
}
|
||||
@@ -55,6 +55,43 @@ impl<T: Math + Copy> Vector3<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lerp(&self, other: &Vector3<T>, t: T) -> Self {
|
||||
Vector3 {
|
||||
x: self.x + (other.x - self.x) * t,
|
||||
y: self.y + (other.y - self.y) * t,
|
||||
z: self.z + (other.z - self.z) * t,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(&self) -> Vector3<i32>
|
||||
where
|
||||
T: Num + PartialOrd + Copy,
|
||||
{
|
||||
Vector3 {
|
||||
x: if self.x > T::zero() {
|
||||
1
|
||||
} else if self.x < T::zero() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
y: if self.y > T::zero() {
|
||||
1
|
||||
} else if self.y < T::zero() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
z: if self.z > T::zero() {
|
||||
1
|
||||
} else if self.z < T::zero() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn squared_distance_to_vec(&self, other: Self) -> T {
|
||||
self.squared_distance_to(other.x, other.y, other.z)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ use pumpkin_config::{BASIC_CONFIG, advanced_config};
|
||||
use pumpkin_data::{
|
||||
BlockState,
|
||||
damage::DamageType,
|
||||
entity::{EffectType, EntityStatus, EntityType},
|
||||
entity::{EffectType, EntityPose, EntityStatus, EntityType},
|
||||
item::Operation,
|
||||
particle::Particle,
|
||||
sound::{Sound, SoundCategory},
|
||||
@@ -716,6 +716,26 @@ impl Player {
|
||||
self.living_entity.entity.pos.load()
|
||||
}
|
||||
|
||||
pub fn eye_position(&self) -> Vector3<f64> {
|
||||
let eye_height = if self.living_entity.entity.pose.load() == EntityPose::Crouching {
|
||||
1.27
|
||||
} else {
|
||||
f64::from(self.living_entity.entity.standing_eye_height)
|
||||
};
|
||||
Vector3::new(
|
||||
self.living_entity.entity.pos.load().x,
|
||||
self.living_entity.entity.pos.load().y + eye_height,
|
||||
self.living_entity.entity.pos.load().z,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn rotation(&self) -> (f32, f32) {
|
||||
(
|
||||
self.living_entity.entity.yaw.load(),
|
||||
self.living_entity.entity.pitch.load(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Updates the current abilities the player has.
|
||||
pub async fn send_abilities_update(&self) {
|
||||
let mut b = 0i8;
|
||||
|
||||
183
pumpkin/src/item/items/bucket.rs
Normal file
183
pumpkin/src/item/items/bucket.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
use pumpkin_protocol::client::play::CSetContainerSlot;
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
|
||||
use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem};
|
||||
use crate::world::{BlockFlags, World};
|
||||
|
||||
pub struct EmptyBucketItem;
|
||||
pub struct FilledBucketItem;
|
||||
|
||||
impl ItemMetadata for EmptyBucketItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[Item::BUCKET.id].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemMetadata for FilledBucketItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[
|
||||
Item::WATER_BUCKET.id,
|
||||
Item::LAVA_BUCKET.id,
|
||||
// TODO drink milk
|
||||
// Item::MILK_BUCKET.id,
|
||||
// TODO implement these buckets, and getting the item from the world
|
||||
// Item::POWDER_SNOW_BUCKET.id,
|
||||
// Item::AXOLOTL_BUCKET.id,
|
||||
// Item::COD_BUCKET.id,
|
||||
// Item::SALMON_BUCKET.id,
|
||||
// Item::TROPICAL_FISH_BUCKET.id,
|
||||
// Item::PUFFERFISH_BUCKET.id,
|
||||
// Item::TADPOLE_BUCKET.id,
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_start_and_end_pos(player: &Player) -> (Vector3<f64>, Vector3<f64>) {
|
||||
let start_pos = player.eye_position();
|
||||
let (yaw, pitch) = player.rotation();
|
||||
let (yaw_rad, pitch_rad) = (f64::from(yaw.to_radians()), f64::from(pitch.to_radians()));
|
||||
let block_interaction_range = 4.5; // This is not the same as the block_interaction_range in the
|
||||
// player entity.
|
||||
let direction = Vector3::new(
|
||||
-yaw_rad.sin() * pitch_rad.cos() * block_interaction_range,
|
||||
-pitch_rad.sin() * block_interaction_range,
|
||||
pitch_rad.cos() * yaw_rad.cos() * block_interaction_range,
|
||||
);
|
||||
|
||||
let end_pos = start_pos.add(&direction);
|
||||
(start_pos, end_pos)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinItem for EmptyBucketItem {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn normal_use(&self, _item: &Item, player: &Player) {
|
||||
let world = player.world().await.clone();
|
||||
let (start_pos, end_pos) = get_start_and_end_pos(player);
|
||||
|
||||
let checker = async |pos: &BlockPos, world_inner: &Arc<World>| {
|
||||
let Ok(state_id) = world_inner.get_block_state_id(pos).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
state_id == Block::WATER.default_state_id || state_id == Block::LAVA.default_state_id
|
||||
};
|
||||
|
||||
let (block_pos, _) = world.raytrace(start_pos, end_pos, checker).await;
|
||||
|
||||
if let Some(pos) = block_pos {
|
||||
let Ok(state_id) = world.get_block_state_id(&pos).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
world
|
||||
.set_block_state(&pos, Block::AIR.id, BlockFlags::NOTIFY_NEIGHBORS)
|
||||
.await;
|
||||
let mut inventory = player.inventory().lock().await;
|
||||
let selected = inventory.get_selected_slot();
|
||||
let item_type = if state_id == Block::WATER.default_state_id {
|
||||
Item::WATER_BUCKET
|
||||
} else {
|
||||
Item::LAVA_BUCKET
|
||||
};
|
||||
let item_stack = Some(ItemStack::new(1, item_type.clone()));
|
||||
let slot_data = ItemStackSerializer::from(item_stack.clone());
|
||||
let game_mode = player.gamemode.load();
|
||||
if game_mode == GameMode::Creative {
|
||||
let slot = inventory.get_pickup_item_slot(item_type.id);
|
||||
if let Some(slot) = slot {
|
||||
if let Err(err) = inventory.set_slot(slot, item_stack, false) {
|
||||
log::error!("Failed to set slot: {err}");
|
||||
} else {
|
||||
let dest_packet = CSetContainerSlot::new(
|
||||
PlayerInventory::CONTAINER_ID,
|
||||
inventory.state_id as i32,
|
||||
slot as i16,
|
||||
&slot_data,
|
||||
);
|
||||
player.client.enqueue_packet(&dest_packet).await;
|
||||
}
|
||||
}
|
||||
} else if let Err(err) = inventory.set_slot(selected, item_stack.clone(), false) {
|
||||
log::error!("Failed to set slot: {err}");
|
||||
} else {
|
||||
let dest_packet = CSetContainerSlot::new(
|
||||
PlayerInventory::CONTAINER_ID,
|
||||
inventory.state_id as i32,
|
||||
selected as i16,
|
||||
&slot_data,
|
||||
);
|
||||
player.client.enqueue_packet(&dest_packet).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinItem for FilledBucketItem {
|
||||
async fn normal_use(&self, item: &Item, player: &Player) {
|
||||
if item.id == Item::MILK_BUCKET.id {
|
||||
// TODO implement milk bucket
|
||||
return;
|
||||
}
|
||||
|
||||
let world = player.world().await.clone();
|
||||
let (start_pos, end_pos) = get_start_and_end_pos(player);
|
||||
let checker = async |pos: &BlockPos, world_inner: &Arc<World>| {
|
||||
let Ok(state_id) = world_inner.get_block_state_id(pos).await else {
|
||||
return false;
|
||||
};
|
||||
if Fluid::from_state_id(state_id).is_some() {
|
||||
return false;
|
||||
}
|
||||
state_id != Block::AIR.id
|
||||
};
|
||||
|
||||
let (block_pos, block_direction) = world.raytrace(start_pos, end_pos, checker).await;
|
||||
|
||||
if let (Some(pos), Some(direction)) = (block_pos, block_direction) {
|
||||
world
|
||||
.set_block_state(
|
||||
&pos.offset(direction.to_offset()),
|
||||
// Block::WATER.default_state_id,
|
||||
if item.id == Item::WATER_BUCKET.id {
|
||||
Block::WATER.default_state_id
|
||||
} else {
|
||||
Block::LAVA.default_state_id
|
||||
},
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
let mut inventory = player.inventory().lock().await;
|
||||
let selected = inventory.get_selected_slot();
|
||||
let item = Some(ItemStack::new(1, Item::BUCKET));
|
||||
let slot_data = ItemStackSerializer::from(item.clone());
|
||||
if let Err(err) = inventory.set_slot(selected, item, false) {
|
||||
log::error!("Failed to set slot: {err}");
|
||||
} else {
|
||||
let dest_packet = CSetContainerSlot::new(
|
||||
PlayerInventory::CONTAINER_ID,
|
||||
inventory.state_id as i32,
|
||||
selected as i16,
|
||||
&slot_data,
|
||||
);
|
||||
player.client.enqueue_packet(&dest_packet).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod axe;
|
||||
mod bucket;
|
||||
mod egg;
|
||||
mod flint_and_steel;
|
||||
mod hoe;
|
||||
@@ -9,6 +10,7 @@ mod sword;
|
||||
mod trident;
|
||||
|
||||
use axe::AxeItem;
|
||||
use bucket::{EmptyBucketItem, FilledBucketItem};
|
||||
use egg::EggItem;
|
||||
use flint_and_steel::FlintAndSteelItem;
|
||||
use hoe::HoeItem;
|
||||
@@ -30,6 +32,8 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(FlintAndSteelItem);
|
||||
manager.register(SwordItem);
|
||||
manager.register(TridentItem);
|
||||
manager.register(EmptyBucketItem);
|
||||
manager.register(FilledBucketItem);
|
||||
manager.register(ShovelItem);
|
||||
manager.register(AxeItem);
|
||||
manager.register(HoneyCombItem);
|
||||
|
||||
@@ -1418,7 +1418,7 @@ impl Player {
|
||||
.item_registry
|
||||
.use_on_block(&stack.item, self, location, face, &block, server)
|
||||
.await;
|
||||
|
||||
self.update_sequence(use_item_on.sequence.0);
|
||||
let action_result = server
|
||||
.block_registry
|
||||
.use_with_item(&block, self, location, &stack.item, server, world)
|
||||
@@ -1484,13 +1484,19 @@ impl Player {
|
||||
world.add_block_entity(Arc::new(updated_sign)).await;
|
||||
}
|
||||
|
||||
pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Server) {
|
||||
pub async fn handle_use_item(&self, use_item: &SUseItem, server: &Server) {
|
||||
if !self.has_client_loaded() {
|
||||
return;
|
||||
}
|
||||
if let Some(held) = self.inventory().lock().await.held_item() {
|
||||
server.item_registry.on_use(&held.item, self).await;
|
||||
let held = {
|
||||
let inventory = self.inventory().lock().await;
|
||||
inventory.held_item().cloned()
|
||||
};
|
||||
|
||||
if held.is_some() {
|
||||
server.item_registry.on_use(&held.unwrap().item, self).await;
|
||||
}
|
||||
self.update_sequence(use_item.sequence.0);
|
||||
}
|
||||
|
||||
pub async fn handle_set_held_item(&self, held: SSetHeldItem) {
|
||||
|
||||
@@ -1729,4 +1729,106 @@ impl World {
|
||||
chunk.block_entities.remove(block_pos);
|
||||
chunk.dirty = true;
|
||||
}
|
||||
|
||||
pub async fn raytrace(
|
||||
self: &Arc<Self>,
|
||||
start_pos: Vector3<f64>,
|
||||
end_pos: Vector3<f64>,
|
||||
hit_check: impl AsyncFn(&BlockPos, &Arc<Self>) -> bool,
|
||||
) -> (Option<BlockPos>, Option<BlockDirection>) {
|
||||
if start_pos == end_pos {
|
||||
return (None, None);
|
||||
}
|
||||
|
||||
let adjust = -1.0e-7f64;
|
||||
let to = end_pos.lerp(&start_pos, adjust);
|
||||
let from = start_pos.lerp(&end_pos, adjust);
|
||||
|
||||
let mut block = BlockPos::floored(from.x, from.y, from.z);
|
||||
|
||||
if hit_check(&block, self).await {
|
||||
return (Some(block), None);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if hit_check(&block, self).await {
|
||||
return (Some(block), Some(block_direction));
|
||||
}
|
||||
}
|
||||
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user