feat: add fishing rod
This commit is contained in:
parent
1abfb195de
commit
4f69e2cb7a
@ -266,7 +266,12 @@ impl<W: Write> ser::Serializer for &mut Serializer<W> {
|
||||
|
||||
// Just skip serializing if value is none
|
||||
fn serialize_none(self) -> Result<()> {
|
||||
Ok(())
|
||||
match self.state {
|
||||
State::FirstListElement { .. } | State::ListElement | State::CheckedListElement => Err(
|
||||
Error::SerdeError("NBT lists do not support null/none values".to_string()),
|
||||
),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
|
||||
|
||||
@ -52,7 +52,9 @@ impl LinearV2Superblock {
|
||||
|
||||
fn from_bytes(buf: &[u8]) -> Result<Self, ChunkReadingError> {
|
||||
if buf.len() < Self::SIZE {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let mut b = buf;
|
||||
let mut sig = [0u8; 8];
|
||||
@ -150,14 +152,18 @@ impl NbtFeatures {
|
||||
let mut map = HashMap::new();
|
||||
loop {
|
||||
if !buf.has_remaining() {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let key_len = buf.get_u8();
|
||||
if key_len == 0 {
|
||||
break;
|
||||
}
|
||||
if buf.remaining() < key_len as usize + 4 {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let key_bytes: Vec<u8> = (0..key_len).map(|_| buf.get_u8()).collect();
|
||||
let key = String::from_utf8(key_bytes).map_err(|_| ChunkReadingError::InvalidHeader)?;
|
||||
@ -199,7 +205,9 @@ impl BucketSizeEntry {
|
||||
|
||||
fn from_bytes(buf: &mut impl Buf) -> Result<Self, ChunkReadingError> {
|
||||
if buf.remaining() < Self::SIZE {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
Ok(Self {
|
||||
size: buf.get_u32(),
|
||||
@ -251,7 +259,9 @@ impl BucketChunkEntry {
|
||||
|
||||
fn read_from(buf: &mut Bytes) -> Result<Self, ChunkReadingError> {
|
||||
if buf.remaining() < Self::FIXED_SIZE {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let size = buf.get_u32() as usize;
|
||||
let timestamp = buf.get_u64();
|
||||
@ -266,7 +276,9 @@ impl BucketChunkEntry {
|
||||
"Linear v2: not enough bytes for chunk (need {size}, have {})",
|
||||
buf.remaining()
|
||||
);
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let data = buf.split_to(size);
|
||||
Ok(Self {
|
||||
@ -440,7 +452,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
let mut buf = raw_file;
|
||||
|
||||
if buf.remaining() < LinearV2Superblock::SIZE {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let superblock_bytes = buf.split_to(LinearV2Superblock::SIZE);
|
||||
let superblock = LinearV2Superblock::from_bytes(&superblock_bytes)?;
|
||||
@ -448,7 +462,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
let bucket_count = Self::bucket_count(grid_size);
|
||||
|
||||
if buf.remaining() < ChunkBitmap::SIZE {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
// We read the bitmap but per the spec it is not yet reliable, so we
|
||||
// do not use it to short-circuit — actual presence is determined by
|
||||
@ -461,7 +477,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
|
||||
let total_bucket_meta = bucket_count * BucketSizeEntry::SIZE;
|
||||
if buf.remaining() < total_bucket_meta {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let mut bucket_entries: Vec<BucketSizeEntry> = Vec::with_capacity(bucket_count);
|
||||
for _ in 0..bucket_count {
|
||||
@ -472,7 +490,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
// consuming the bucket data bytes yet.
|
||||
let total_compressed: usize = bucket_entries.iter().map(|e| e.size as usize).sum();
|
||||
if buf.remaining() < total_compressed + SIGNATURE.len() {
|
||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||
ErrorKind::UnexpectedEof,
|
||||
)));
|
||||
}
|
||||
let footer_offset = total_compressed;
|
||||
{
|
||||
@ -510,7 +530,7 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
.map_err(|_| ChunkReadingError::RegionIsInvalid)?;
|
||||
decoder
|
||||
.read_to_end(&mut decompressed)
|
||||
.map_err(|e| ChunkReadingError::IoError(e.kind()))?
|
||||
.map_err(ChunkReadingError::IoError)?
|
||||
};
|
||||
buf.advance(compressed_size);
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::chunk::format::anvil::{CHUNK_COUNT, SingleChunkDataSerializer};
|
||||
use crate::chunk::format::anvil::SingleChunkDataSerializer;
|
||||
use crate::chunk::io::{ChunkSerializer, LoadedData};
|
||||
use crate::chunk::{ChunkReadingError, ChunkWritingError};
|
||||
use bytes::Bytes;
|
||||
@ -9,7 +10,6 @@ use pumpkin_util::math::vector2::Vector2;
|
||||
use ruzstd::decoding::StreamingDecoder;
|
||||
use ruzstd::encoding::{CompressionLevel, compress_to_vec};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
pub struct PumpFile<D> {
|
||||
pub data: PumpData,
|
||||
@ -20,17 +20,13 @@ pub struct PumpFile<D> {
|
||||
pub struct PumpData {
|
||||
pub x: i32,
|
||||
pub z: i32,
|
||||
pub chunks: Vec<Option<Vec<u8>>>,
|
||||
pub chunks: BTreeMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<D> Default for PumpFile<D> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data: PumpData {
|
||||
x: 0,
|
||||
z: 0,
|
||||
chunks: vec![None; CHUNK_COUNT],
|
||||
},
|
||||
data: PumpData::default(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -66,10 +62,10 @@ where
|
||||
|
||||
fn read(r: Bytes) -> Result<Self, ChunkReadingError> {
|
||||
let mut decoder = StreamingDecoder::new(&r[..])
|
||||
.map_err(|e| ChunkReadingError::IoError(std::io::Error::other(e.to_string()).kind()))?;
|
||||
.map_err(|e| ChunkReadingError::IoError(std::io::Error::other(e.to_string())))?;
|
||||
let mut decompressed = Vec::new();
|
||||
std::io::Read::read_to_end(&mut decoder, &mut decompressed)
|
||||
.map_err(|e| ChunkReadingError::IoError(e.kind()))?;
|
||||
.map_err(ChunkReadingError::IoError)?;
|
||||
|
||||
let data: PumpData = pumpkin_nbt::from_bytes_unnamed(std::io::Cursor::new(decompressed))
|
||||
.map_err(|e| {
|
||||
@ -101,11 +97,7 @@ where
|
||||
.await
|
||||
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
|
||||
|
||||
if index < self.data.chunks.len() {
|
||||
self.data.chunks[index] = Some(bytes.to_vec());
|
||||
} else {
|
||||
warn!("Chunk position {}, {} out of range for Pump region", x, z);
|
||||
}
|
||||
self.data.chunks.insert(index.to_string(), bytes.to_vec());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -120,8 +112,7 @@ where
|
||||
let rel_z = pos.y.rem_euclid(32);
|
||||
let index = (rel_x + rel_z * 32) as usize;
|
||||
|
||||
if index < self.data.chunks.len() {
|
||||
if let Some(chunk_bytes) = &self.data.chunks[index] {
|
||||
if let Some(chunk_bytes) = self.data.chunks.get(&index.to_string()) {
|
||||
let bytes = Bytes::copy_from_slice(chunk_bytes);
|
||||
match D::from_bytes(&bytes, pos) {
|
||||
Ok(data) => {
|
||||
@ -134,18 +125,6 @@ where
|
||||
} else {
|
||||
let _ = stream.send(LoadedData::Missing(pos)).await;
|
||||
}
|
||||
} else {
|
||||
let _ = stream
|
||||
.send(LoadedData::Error((
|
||||
pos,
|
||||
ChunkReadingError::ParsingError(
|
||||
crate::chunk::ChunkParsingError::ErrorDeserializingChunk(
|
||||
"Chunk out of range".to_string(),
|
||||
),
|
||||
),
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkSerializerLazyLoader<S> {
|
||||
trace!("File not found, using default for: {}", self.path.display());
|
||||
Ok(S::default())
|
||||
}
|
||||
Err(err) => Err(ChunkReadingError::IoError(err.kind())),
|
||||
Err(err) => Err(ChunkReadingError::IoError(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -338,8 +338,10 @@ where
|
||||
error!("I/O error reading region before write: {err}");
|
||||
return Err(ChunkWritingError::IoError(err));
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(ChunkWritingError::IoError(std::io::ErrorKind::Other));
|
||||
Err(err) => {
|
||||
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
||||
err.to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -377,7 +379,7 @@ where
|
||||
serializer
|
||||
.write(&path)
|
||||
.await
|
||||
.map_err(|e| ChunkWritingError::IoError(e.kind()))?
|
||||
.map_err(ChunkWritingError::IoError)?
|
||||
// Read-lock released here.
|
||||
};
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ pub const SUBCHUNK_VOLUME: usize = CHUNK_AREA * CHUNK_WIDTH;
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ChunkReadingError {
|
||||
#[error("Io error: {0}")]
|
||||
IoError(std::io::ErrorKind),
|
||||
IoError(std::io::Error),
|
||||
#[error("Invalid header")]
|
||||
InvalidHeader,
|
||||
#[error("Region is invalid")]
|
||||
@ -46,7 +46,7 @@ pub enum ChunkReadingError {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ChunkWritingError {
|
||||
#[error("Io error: {0}")]
|
||||
IoError(std::io::ErrorKind),
|
||||
IoError(std::io::Error),
|
||||
#[error("Compression error {0}")]
|
||||
Compression(CompressionError),
|
||||
#[error("Chunk serializing error: {0}")]
|
||||
|
||||
@ -549,7 +549,9 @@ impl GenerationSchedule {
|
||||
}
|
||||
let sc = Arc::strong_count(&chunk);
|
||||
if sc == 1 {
|
||||
if chunk.is_dirty() {
|
||||
chunks.push((pos, Chunk::Level(chunk)));
|
||||
}
|
||||
self.chunk_map.remove(&pos);
|
||||
} else {
|
||||
warn!(
|
||||
|
||||
@ -20,7 +20,7 @@ use std::{collections::HashMap, sync::atomic::AtomicI32};
|
||||
use tracing::warn;
|
||||
|
||||
use super::experience_orb::ExperienceOrbEntity;
|
||||
use super::{Entity, EntityBase, NBTStorage};
|
||||
use super::{Entity, EntityBase, NBTStorage, NBTStorageInit};
|
||||
use crate::block::OnLandedUponArgs;
|
||||
use crate::entity::attributes::AttributeInstance;
|
||||
use crate::entity::attributes::Modifier;
|
||||
@ -47,8 +47,8 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
Animation, CEntityAnimation, CHurtAnimation, CSetPlayerInventory, CTakeItemEntity,
|
||||
CUpdateMobEffect,
|
||||
Animation, CEntityAnimation, CEntityStatus, CHurtAnimation, CSetPlayerInventory,
|
||||
CTakeItemEntity, CUpdateMobEffect,
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
@ -1968,14 +1968,14 @@ impl EntityBase for LivingEntity {
|
||||
disable_chance = 1.0;
|
||||
}
|
||||
|
||||
if rand::random::<f32>() < disable_chance {
|
||||
if let Some(victim_player) = caller.get_player() {
|
||||
if rand::random::<f32>() < disable_chance
|
||||
&& let Some(victim_player) = caller.get_player()
|
||||
{
|
||||
victim_player
|
||||
.start_cooldown("minecraft:shield".to_string(), 100)
|
||||
.await;
|
||||
self.clear_active_hand().await;
|
||||
|
||||
use pumpkin_protocol::java::client::play::CEntityStatus;
|
||||
world
|
||||
.broadcast_packet_all(&CEntityStatus::new(
|
||||
self.entity.entity_id,
|
||||
@ -1985,7 +1985,6 @@ impl EntityBase for LivingEntity {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let active_hand = self.active_hand.lock().await;
|
||||
if let Some(hand) = *active_hand {
|
||||
|
||||
@ -497,6 +497,7 @@ pub struct Player {
|
||||
pub tab_list_latency: AtomicI32,
|
||||
pub tab_list_listed: AtomicBool,
|
||||
pub enchantment_seed: AtomicI32,
|
||||
pub fishing_bobber: AtomicI32,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
@ -633,6 +634,7 @@ impl Player {
|
||||
tab_list_order: AtomicI32::new(0),
|
||||
tab_list_latency: AtomicI32::new(0),
|
||||
tab_list_listed: AtomicBool::new(false),
|
||||
fishing_bobber: AtomicI32::new(-1),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
255
pumpkin/src/entity/projectile/fishing_bobber.rs
Normal file
255
pumpkin/src/entity/projectile/fishing_bobber.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
|
||||
use crate::entity::projectile::{ProjectileHit, is_projectile};
|
||||
use crate::{
|
||||
entity::{
|
||||
Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity, player::Player,
|
||||
},
|
||||
server::Server,
|
||||
};
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::meta_data_type::MetaDataType;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::tracked_data::TrackedData;
|
||||
use pumpkin_protocol::java::client::play::Metadata;
|
||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
pub struct FishingBobberEntity {
|
||||
pub entity: Entity,
|
||||
pub owner_id: i32,
|
||||
pub hooked_entity_id: AtomicI32,
|
||||
pub in_ground: AtomicBool,
|
||||
pub has_hit: AtomicBool,
|
||||
pub wait_countdown: AtomicI32,
|
||||
pub bite_countdown: AtomicI32,
|
||||
}
|
||||
|
||||
impl FishingBobberEntity {
|
||||
const WATER_INERTIA: f64 = 0.8;
|
||||
const AIR_INERTIA: f64 = 0.92;
|
||||
const GRAVITY: f64 = 0.03;
|
||||
|
||||
pub fn new(entity: Entity, owner: &Player) -> Self {
|
||||
let mut owner_pos = owner.living_entity.entity.pos.load();
|
||||
owner_pos.y += owner.living_entity.entity.get_eye_height() - 0.1;
|
||||
entity.pos.store(owner_pos);
|
||||
|
||||
Self {
|
||||
entity,
|
||||
owner_id: owner.living_entity.entity.entity_id,
|
||||
hooked_entity_id: AtomicI32::new(0),
|
||||
in_ground: AtomicBool::new(false),
|
||||
has_hit: AtomicBool::new(false),
|
||||
wait_countdown: AtomicI32::new(rand::random::<i32>().abs() % 600 + 100),
|
||||
bite_countdown: AtomicI32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reel_in(&self, player: &Player) -> i32 {
|
||||
let world = self.entity.world.load();
|
||||
let hooked_id = self.hooked_entity_id.load(Ordering::Relaxed);
|
||||
|
||||
if hooked_id != 0
|
||||
&& let Some(hooked) = world.get_entity_by_id(hooked_id)
|
||||
{
|
||||
let player_pos = player.living_entity.entity.pos.load();
|
||||
let hooked_pos = hooked.get_entity().pos.load();
|
||||
let delta = player_pos - hooked_pos;
|
||||
let motion =
|
||||
delta
|
||||
.multiply(0.1, 0.1, 0.1)
|
||||
.add_raw(0.0, delta.length().sqrt() * 0.08, 0.0);
|
||||
hooked.get_entity().add_velocity(motion).await;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if self.bite_countdown.load(Ordering::Relaxed) > 0 {
|
||||
// Caught something!
|
||||
// TODO: Use actual loot tables. For now, just give a raw cod.
|
||||
use pumpkin_data::item::Item;
|
||||
let _item_stack = ItemStack::new(1, &Item::COD);
|
||||
// player.inventory().add_item(item_stack).await; // Need public add_item
|
||||
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityExperienceOrbPickup,
|
||||
SoundCategory::Neutral,
|
||||
&player.position(),
|
||||
)
|
||||
.await;
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn process_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {
|
||||
let entity = self.get_entity();
|
||||
let world = entity.world.load();
|
||||
|
||||
if self.in_ground.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hooked_id = self.hooked_entity_id.load(Ordering::Relaxed);
|
||||
if hooked_id != 0 {
|
||||
if let Some(hooked) = world.get_entity_by_id(hooked_id) {
|
||||
if hooked.get_entity().removed.load(Ordering::Relaxed) {
|
||||
self.hooked_entity_id.store(0, Ordering::Relaxed);
|
||||
} else {
|
||||
let mut hooked_pos = hooked.get_entity().pos.load();
|
||||
hooked_pos.y += hooked.get_entity().get_eye_height() * 0.8;
|
||||
entity.set_pos(hooked_pos);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
self.hooked_entity_id.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
let mut velocity = entity.velocity.load();
|
||||
let start_pos = entity.pos.load();
|
||||
|
||||
if entity.touching_water.load(Ordering::Relaxed) {
|
||||
velocity.y += 0.02; // Buoyancy
|
||||
|
||||
let bite = self.bite_countdown.load(Ordering::Relaxed);
|
||||
if bite > 0 {
|
||||
self.bite_countdown.store(bite - 1, Ordering::Relaxed);
|
||||
if bite % 5 == 0 {
|
||||
world
|
||||
.spawn_particle(
|
||||
entity.pos.load(),
|
||||
Vector3::new(0.1f32, 0.1f32, 0.1f32),
|
||||
0.0,
|
||||
5,
|
||||
pumpkin_data::particle::Particle::Bubble,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
let wait = self.wait_countdown.load(Ordering::Relaxed);
|
||||
if wait > 0 {
|
||||
self.wait_countdown.store(wait - 1, Ordering::Relaxed);
|
||||
} else {
|
||||
// Start bite
|
||||
self.bite_countdown.store(40, Ordering::Relaxed);
|
||||
self.wait_countdown
|
||||
.store(rand::random::<i32>().abs() % 600 + 100, Ordering::Relaxed);
|
||||
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityFishingBobberSplash,
|
||||
SoundCategory::Neutral,
|
||||
&entity.pos.load(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
velocity.y -= Self::GRAVITY;
|
||||
}
|
||||
|
||||
let inertia = if entity.touching_water.load(Ordering::Relaxed) {
|
||||
Self::WATER_INERTIA
|
||||
} else {
|
||||
Self::AIR_INERTIA
|
||||
};
|
||||
velocity = velocity.multiply(inertia, inertia, inertia);
|
||||
entity.velocity.store(velocity);
|
||||
|
||||
let new_pos = start_pos.add(&velocity);
|
||||
|
||||
let search_box = BoundingBox::new(
|
||||
Vector3::new(
|
||||
start_pos.x.min(new_pos.x),
|
||||
start_pos.y.min(new_pos.y),
|
||||
start_pos.z.min(new_pos.z),
|
||||
),
|
||||
Vector3::new(
|
||||
start_pos.x.max(new_pos.x),
|
||||
start_pos.y.max(new_pos.y),
|
||||
start_pos.z.max(new_pos.z),
|
||||
),
|
||||
)
|
||||
.expand(0.3, 0.3, 0.3);
|
||||
|
||||
// Basic block collision to stop bobber
|
||||
let (block_cols, _) = world
|
||||
.get_block_collisions(search_box, caller.as_ref())
|
||||
.await;
|
||||
if !block_cols.is_empty() {
|
||||
self.in_ground.store(true, Ordering::Relaxed);
|
||||
entity.velocity.store(Vector3::new(0.0, 0.0, 0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
entity.set_pos(new_pos);
|
||||
|
||||
let candidates = world.get_entities_at_box(&search_box);
|
||||
for cand in candidates {
|
||||
if cand.get_entity().entity_id == self.owner_id
|
||||
|| cand.get_entity().entity_id == entity.entity_id
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_projectile(cand.get_entity().entity_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ebb = cand.get_entity().bounding_box.load().expand(0.3, 0.3, 0.3);
|
||||
if ebb.intersects(&search_box) {
|
||||
self.hooked_entity_id
|
||||
.store(cand.get_entity().entity_id, Ordering::Relaxed);
|
||||
entity
|
||||
.send_meta_data(&[Metadata::new(
|
||||
TrackedData::HOOKED_ENTITY,
|
||||
MetaDataType::INT,
|
||||
cand.get_entity().entity_id + 1,
|
||||
)])
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for FishingBobberEntity {}
|
||||
|
||||
impl EntityBase for FishingBobberEntity {
|
||||
fn get_entity(&self) -> &Entity {
|
||||
&self.entity
|
||||
}
|
||||
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn cast_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||
self
|
||||
}
|
||||
|
||||
fn on_hit(&self, _hit: ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.has_hit.store(true, Ordering::Relaxed);
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(
|
||||
&'a self,
|
||||
caller: &'a Arc<dyn EntityBase>,
|
||||
server: &'a Server,
|
||||
) -> EntityBaseFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.process_tick(caller, server).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ pub mod ender_pearl;
|
||||
pub mod eye_of_ender;
|
||||
pub mod fireball;
|
||||
pub mod firework_rocket;
|
||||
pub mod fishing_bobber;
|
||||
pub mod lingering_potion;
|
||||
pub mod shulker_bullet;
|
||||
pub mod small_fireball;
|
||||
@ -35,6 +36,7 @@ pub fn is_projectile(entity_type: &EntityType) -> bool {
|
||||
|| *entity_type == EntityType::SHULKER_BULLET
|
||||
|| *entity_type == EntityType::FIREBALL
|
||||
|| *entity_type == EntityType::SMALL_FIREBALL
|
||||
|| *entity_type == EntityType::FISHING_BOBBER
|
||||
}
|
||||
|
||||
pub struct ThrownItemEntity {
|
||||
|
||||
@ -134,13 +134,15 @@ impl CrossbowItem {
|
||||
.cloned();
|
||||
|
||||
if let Some(charged) = projectiles {
|
||||
let mut has_multishot = false;
|
||||
if let Some(enchantments) = stack.get_data_component::<EnchantmentsImpl>() {
|
||||
has_multishot = enchantments
|
||||
let has_multishot =
|
||||
stack
|
||||
.get_data_component::<EnchantmentsImpl>()
|
||||
.is_some_and(|enchantments| {
|
||||
enchantments
|
||||
.enchantment
|
||||
.iter()
|
||||
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT);
|
||||
}
|
||||
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
|
||||
});
|
||||
|
||||
let world = player.world();
|
||||
world
|
||||
|
||||
89
pumpkin/src/item/items/fishing_rod.rs
Normal file
89
pumpkin/src/item/items/fishing_rod.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::projectile::fishing_bobber::FishingBobberEntity;
|
||||
use crate::entity::{Entity, EntityBase};
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
|
||||
pub struct FishingRodItem;
|
||||
|
||||
impl ItemMetadata for FishingRodItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
Box::new([Item::FISHING_ROD.id])
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBehaviour for FishingRodItem {
|
||||
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 bobber_id = player.fishing_bobber.load(Ordering::Relaxed);
|
||||
|
||||
if bobber_id == -1 {
|
||||
// Cast
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityFishingBobberThrow,
|
||||
SoundCategory::Neutral,
|
||||
&player.position(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let bobber_entity = Entity::new(
|
||||
world.clone(),
|
||||
player.position(),
|
||||
&EntityType::FISHING_BOBBER,
|
||||
);
|
||||
let bobber = FishingBobberEntity::new(bobber_entity, player);
|
||||
|
||||
let look_vec = player.living_entity.get_looking_vector();
|
||||
bobber
|
||||
.entity
|
||||
.velocity
|
||||
.store(look_vec.multiply(1.5, 1.5, 1.5));
|
||||
|
||||
player
|
||||
.fishing_bobber
|
||||
.store(bobber.entity.entity_id, Ordering::Relaxed);
|
||||
|
||||
let bobber_arc: Arc<FishingBobberEntity> = Arc::new(bobber);
|
||||
world.spawn_entity(bobber_arc).await;
|
||||
} else {
|
||||
// Reel in
|
||||
if let Some(bobber_base) = world.get_entity_by_id(bobber_id) {
|
||||
if let Some(bobber) =
|
||||
bobber_base.cast_any().downcast_ref::<FishingBobberEntity>()
|
||||
{
|
||||
let _result = bobber.reel_in(player).await;
|
||||
// TODO: give items
|
||||
}
|
||||
bobber_base.get_entity().remove().await;
|
||||
}
|
||||
player.fishing_bobber.store(-1, Ordering::Relaxed);
|
||||
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityFishingBobberRetrieve,
|
||||
SoundCategory::Neutral,
|
||||
&player.position(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ pub mod end_crystal;
|
||||
pub mod ender_eye;
|
||||
pub mod ender_pearl;
|
||||
pub mod firework_rocket;
|
||||
pub mod fishing_rod;
|
||||
pub mod glowing_ink_sac;
|
||||
pub mod hoe;
|
||||
pub mod honeycomb;
|
||||
@ -31,12 +32,14 @@ pub mod wind_charge;
|
||||
use crate::item::items::armor_stand::ArmorStandItem;
|
||||
use crate::item::items::boat::BoatItem;
|
||||
use crate::item::items::end_crystal::EndCrystalItem;
|
||||
use crate::item::items::firework_rocket::FireworkRocketItem;
|
||||
use crate::item::items::map::MapItem;
|
||||
use crate::item::items::minecart::MinecartItem;
|
||||
use crate::item::items::name_tag::NameTagItem;
|
||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||
use crate::item::items::wind_charge::WindChargeItem;
|
||||
use firework_rocket::FireworkRocketItem;
|
||||
use fishing_rod::FishingRodItem;
|
||||
use glowing_ink_sac::GlowingInkSacItem;
|
||||
|
||||
use super::registry::ItemRegistry;
|
||||
use crate::item::items::potions::{LingeringPotionItem, PotionItem, SplashPotionItem};
|
||||
@ -49,7 +52,6 @@ use dye::DyeItem;
|
||||
use egg::EggItem;
|
||||
use ender_eye::EnderEyeItem;
|
||||
use ender_pearl::EnderPearlItem;
|
||||
use glowing_ink_sac::GlowingInkSacItem;
|
||||
use hoe::HoeItem;
|
||||
use honeycomb::HoneyCombItem;
|
||||
use ignite::fire_charge::FireChargeItem;
|
||||
@ -76,6 +78,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(SwordItem);
|
||||
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.
|
||||
|
||||
@ -881,7 +881,7 @@ impl World {
|
||||
}
|
||||
let block_entity_elapsed = block_entity_start.elapsed();
|
||||
|
||||
//self.level.chunk_loading.lock().unwrap().send_change();
|
||||
self.level.chunk_loading.lock().unwrap().send_change();
|
||||
|
||||
// Tick the End dragon fight (only on THE_END worlds).
|
||||
if let Some(ref fight_mutex) = self.dragon_fight {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user