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
|
// Just skip serializing if value is none
|
||||||
fn serialize_none(self) -> Result<()> {
|
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<()> {
|
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
|
||||||
|
|||||||
@ -52,7 +52,9 @@ impl LinearV2Superblock {
|
|||||||
|
|
||||||
fn from_bytes(buf: &[u8]) -> Result<Self, ChunkReadingError> {
|
fn from_bytes(buf: &[u8]) -> Result<Self, ChunkReadingError> {
|
||||||
if buf.len() < Self::SIZE {
|
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 b = buf;
|
||||||
let mut sig = [0u8; 8];
|
let mut sig = [0u8; 8];
|
||||||
@ -150,14 +152,18 @@ impl NbtFeatures {
|
|||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
loop {
|
loop {
|
||||||
if !buf.has_remaining() {
|
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();
|
let key_len = buf.get_u8();
|
||||||
if key_len == 0 {
|
if key_len == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if buf.remaining() < key_len as usize + 4 {
|
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_bytes: Vec<u8> = (0..key_len).map(|_| buf.get_u8()).collect();
|
||||||
let key = String::from_utf8(key_bytes).map_err(|_| ChunkReadingError::InvalidHeader)?;
|
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> {
|
fn from_bytes(buf: &mut impl Buf) -> Result<Self, ChunkReadingError> {
|
||||||
if buf.remaining() < Self::SIZE {
|
if buf.remaining() < Self::SIZE {
|
||||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||||
|
ErrorKind::UnexpectedEof,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
size: buf.get_u32(),
|
size: buf.get_u32(),
|
||||||
@ -251,7 +259,9 @@ impl BucketChunkEntry {
|
|||||||
|
|
||||||
fn read_from(buf: &mut Bytes) -> Result<Self, ChunkReadingError> {
|
fn read_from(buf: &mut Bytes) -> Result<Self, ChunkReadingError> {
|
||||||
if buf.remaining() < Self::FIXED_SIZE {
|
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 size = buf.get_u32() as usize;
|
||||||
let timestamp = buf.get_u64();
|
let timestamp = buf.get_u64();
|
||||||
@ -266,7 +276,9 @@ impl BucketChunkEntry {
|
|||||||
"Linear v2: not enough bytes for chunk (need {size}, have {})",
|
"Linear v2: not enough bytes for chunk (need {size}, have {})",
|
||||||
buf.remaining()
|
buf.remaining()
|
||||||
);
|
);
|
||||||
return Err(ChunkReadingError::IoError(ErrorKind::UnexpectedEof));
|
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||||
|
ErrorKind::UnexpectedEof,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
let data = buf.split_to(size);
|
let data = buf.split_to(size);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -440,7 +452,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
|||||||
let mut buf = raw_file;
|
let mut buf = raw_file;
|
||||||
|
|
||||||
if buf.remaining() < LinearV2Superblock::SIZE {
|
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_bytes = buf.split_to(LinearV2Superblock::SIZE);
|
||||||
let superblock = LinearV2Superblock::from_bytes(&superblock_bytes)?;
|
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);
|
let bucket_count = Self::bucket_count(grid_size);
|
||||||
|
|
||||||
if buf.remaining() < ChunkBitmap::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
|
// 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
|
// 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;
|
let total_bucket_meta = bucket_count * BucketSizeEntry::SIZE;
|
||||||
if buf.remaining() < total_bucket_meta {
|
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);
|
let mut bucket_entries: Vec<BucketSizeEntry> = Vec::with_capacity(bucket_count);
|
||||||
for _ in 0..bucket_count {
|
for _ in 0..bucket_count {
|
||||||
@ -472,7 +490,9 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
|||||||
// consuming the bucket data bytes yet.
|
// consuming the bucket data bytes yet.
|
||||||
let total_compressed: usize = bucket_entries.iter().map(|e| e.size as usize).sum();
|
let total_compressed: usize = bucket_entries.iter().map(|e| e.size as usize).sum();
|
||||||
if buf.remaining() < total_compressed + SIGNATURE.len() {
|
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;
|
let footer_offset = total_compressed;
|
||||||
{
|
{
|
||||||
@ -510,7 +530,7 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
|||||||
.map_err(|_| ChunkReadingError::RegionIsInvalid)?;
|
.map_err(|_| ChunkReadingError::RegionIsInvalid)?;
|
||||||
decoder
|
decoder
|
||||||
.read_to_end(&mut decompressed)
|
.read_to_end(&mut decompressed)
|
||||||
.map_err(|e| ChunkReadingError::IoError(e.kind()))?
|
.map_err(ChunkReadingError::IoError)?
|
||||||
};
|
};
|
||||||
buf.advance(compressed_size);
|
buf.advance(compressed_size);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
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::io::{ChunkSerializer, LoadedData};
|
||||||
use crate::chunk::{ChunkReadingError, ChunkWritingError};
|
use crate::chunk::{ChunkReadingError, ChunkWritingError};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -9,7 +10,6 @@ use pumpkin_util::math::vector2::Vector2;
|
|||||||
use ruzstd::decoding::StreamingDecoder;
|
use ruzstd::decoding::StreamingDecoder;
|
||||||
use ruzstd::encoding::{CompressionLevel, compress_to_vec};
|
use ruzstd::encoding::{CompressionLevel, compress_to_vec};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
pub struct PumpFile<D> {
|
pub struct PumpFile<D> {
|
||||||
pub data: PumpData,
|
pub data: PumpData,
|
||||||
@ -20,17 +20,13 @@ pub struct PumpFile<D> {
|
|||||||
pub struct PumpData {
|
pub struct PumpData {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
pub chunks: Vec<Option<Vec<u8>>>,
|
pub chunks: BTreeMap<String, Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Default for PumpFile<D> {
|
impl<D> Default for PumpFile<D> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: PumpData {
|
data: PumpData::default(),
|
||||||
x: 0,
|
|
||||||
z: 0,
|
|
||||||
chunks: vec![None; CHUNK_COUNT],
|
|
||||||
},
|
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,10 +62,10 @@ where
|
|||||||
|
|
||||||
fn read(r: Bytes) -> Result<Self, ChunkReadingError> {
|
fn read(r: Bytes) -> Result<Self, ChunkReadingError> {
|
||||||
let mut decoder = StreamingDecoder::new(&r[..])
|
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();
|
let mut decompressed = Vec::new();
|
||||||
std::io::Read::read_to_end(&mut decoder, &mut decompressed)
|
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))
|
let data: PumpData = pumpkin_nbt::from_bytes_unnamed(std::io::Cursor::new(decompressed))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -101,11 +97,7 @@ where
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
|
.map_err(|e| ChunkWritingError::ChunkSerializingError(e.to_string()))?;
|
||||||
|
|
||||||
if index < self.data.chunks.len() {
|
self.data.chunks.insert(index.to_string(), bytes.to_vec());
|
||||||
self.data.chunks[index] = Some(bytes.to_vec());
|
|
||||||
} else {
|
|
||||||
warn!("Chunk position {}, {} out of range for Pump region", x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -120,31 +112,18 @@ where
|
|||||||
let rel_z = pos.y.rem_euclid(32);
|
let rel_z = pos.y.rem_euclid(32);
|
||||||
let index = (rel_x + rel_z * 32) as usize;
|
let index = (rel_x + rel_z * 32) as usize;
|
||||||
|
|
||||||
if index < self.data.chunks.len() {
|
if let Some(chunk_bytes) = self.data.chunks.get(&index.to_string()) {
|
||||||
if let Some(chunk_bytes) = &self.data.chunks[index] {
|
let bytes = Bytes::copy_from_slice(chunk_bytes);
|
||||||
let bytes = Bytes::copy_from_slice(chunk_bytes);
|
match D::from_bytes(&bytes, pos) {
|
||||||
match D::from_bytes(&bytes, pos) {
|
Ok(data) => {
|
||||||
Ok(data) => {
|
let _ = stream.send(LoadedData::Loaded(data)).await;
|
||||||
let _ = stream.send(LoadedData::Loaded(data)).await;
|
}
|
||||||
}
|
Err(e) => {
|
||||||
Err(e) => {
|
let _ = stream.send(LoadedData::Error((pos, e))).await;
|
||||||
let _ = stream.send(LoadedData::Error((pos, e))).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let _ = stream.send(LoadedData::Missing(pos)).await;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = stream
|
let _ = stream.send(LoadedData::Missing(pos)).await;
|
||||||
.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());
|
trace!("File not found, using default for: {}", self.path.display());
|
||||||
Ok(S::default())
|
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}");
|
error!("I/O error reading region before write: {err}");
|
||||||
return Err(ChunkWritingError::IoError(err));
|
return Err(ChunkWritingError::IoError(err));
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(err) => {
|
||||||
return Err(ChunkWritingError::IoError(std::io::ErrorKind::Other));
|
return Err(ChunkWritingError::IoError(std::io::Error::other(
|
||||||
|
err.to_string(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -377,7 +379,7 @@ where
|
|||||||
serializer
|
serializer
|
||||||
.write(&path)
|
.write(&path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ChunkWritingError::IoError(e.kind()))?
|
.map_err(ChunkWritingError::IoError)?
|
||||||
// Read-lock released here.
|
// Read-lock released here.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ pub const SUBCHUNK_VOLUME: usize = CHUNK_AREA * CHUNK_WIDTH;
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ChunkReadingError {
|
pub enum ChunkReadingError {
|
||||||
#[error("Io error: {0}")]
|
#[error("Io error: {0}")]
|
||||||
IoError(std::io::ErrorKind),
|
IoError(std::io::Error),
|
||||||
#[error("Invalid header")]
|
#[error("Invalid header")]
|
||||||
InvalidHeader,
|
InvalidHeader,
|
||||||
#[error("Region is invalid")]
|
#[error("Region is invalid")]
|
||||||
@ -46,7 +46,7 @@ pub enum ChunkReadingError {
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ChunkWritingError {
|
pub enum ChunkWritingError {
|
||||||
#[error("Io error: {0}")]
|
#[error("Io error: {0}")]
|
||||||
IoError(std::io::ErrorKind),
|
IoError(std::io::Error),
|
||||||
#[error("Compression error {0}")]
|
#[error("Compression error {0}")]
|
||||||
Compression(CompressionError),
|
Compression(CompressionError),
|
||||||
#[error("Chunk serializing error: {0}")]
|
#[error("Chunk serializing error: {0}")]
|
||||||
|
|||||||
@ -549,7 +549,9 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
let sc = Arc::strong_count(&chunk);
|
let sc = Arc::strong_count(&chunk);
|
||||||
if sc == 1 {
|
if sc == 1 {
|
||||||
chunks.push((pos, Chunk::Level(chunk)));
|
if chunk.is_dirty() {
|
||||||
|
chunks.push((pos, Chunk::Level(chunk)));
|
||||||
|
}
|
||||||
self.chunk_map.remove(&pos);
|
self.chunk_map.remove(&pos);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
|
|||||||
@ -20,7 +20,7 @@ use std::{collections::HashMap, sync::atomic::AtomicI32};
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use super::experience_orb::ExperienceOrbEntity;
|
use super::experience_orb::ExperienceOrbEntity;
|
||||||
use super::{Entity, EntityBase, NBTStorage};
|
use super::{Entity, EntityBase, NBTStorage, NBTStorageInit};
|
||||||
use crate::block::OnLandedUponArgs;
|
use crate::block::OnLandedUponArgs;
|
||||||
use crate::entity::attributes::AttributeInstance;
|
use crate::entity::attributes::AttributeInstance;
|
||||||
use crate::entity::attributes::Modifier;
|
use crate::entity::attributes::Modifier;
|
||||||
@ -47,8 +47,8 @@ use pumpkin_nbt::compound::NbtCompound;
|
|||||||
use pumpkin_nbt::tag::NbtTag;
|
use pumpkin_nbt::tag::NbtTag;
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
Animation, CEntityAnimation, CHurtAnimation, CSetPlayerInventory, CTakeItemEntity,
|
Animation, CEntityAnimation, CEntityStatus, CHurtAnimation, CSetPlayerInventory,
|
||||||
CUpdateMobEffect,
|
CTakeItemEntity, CUpdateMobEffect,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
codec::item_stack_seralizer::ItemStackSerializer,
|
codec::item_stack_seralizer::ItemStackSerializer,
|
||||||
@ -1968,21 +1968,20 @@ impl EntityBase for LivingEntity {
|
|||||||
disable_chance = 1.0;
|
disable_chance = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if rand::random::<f32>() < disable_chance {
|
if rand::random::<f32>() < disable_chance
|
||||||
if let Some(victim_player) = caller.get_player() {
|
&& let Some(victim_player) = caller.get_player()
|
||||||
victim_player
|
{
|
||||||
.start_cooldown("minecraft:shield".to_string(), 100)
|
victim_player
|
||||||
.await;
|
.start_cooldown("minecraft:shield".to_string(), 100)
|
||||||
self.clear_active_hand().await;
|
.await;
|
||||||
|
self.clear_active_hand().await;
|
||||||
|
|
||||||
use pumpkin_protocol::java::client::play::CEntityStatus;
|
world
|
||||||
world
|
.broadcast_packet_all(&CEntityStatus::new(
|
||||||
.broadcast_packet_all(&CEntityStatus::new(
|
self.entity.entity_id,
|
||||||
self.entity.entity_id,
|
30,
|
||||||
30,
|
))
|
||||||
))
|
.await;
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -497,6 +497,7 @@ pub struct Player {
|
|||||||
pub tab_list_latency: AtomicI32,
|
pub tab_list_latency: AtomicI32,
|
||||||
pub tab_list_listed: AtomicBool,
|
pub tab_list_listed: AtomicBool,
|
||||||
pub enchantment_seed: AtomicI32,
|
pub enchantment_seed: AtomicI32,
|
||||||
|
pub fishing_bobber: AtomicI32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
@ -633,6 +634,7 @@ impl Player {
|
|||||||
tab_list_order: AtomicI32::new(0),
|
tab_list_order: AtomicI32::new(0),
|
||||||
tab_list_latency: AtomicI32::new(0),
|
tab_list_latency: AtomicI32::new(0),
|
||||||
tab_list_listed: AtomicBool::new(false),
|
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 eye_of_ender;
|
||||||
pub mod fireball;
|
pub mod fireball;
|
||||||
pub mod firework_rocket;
|
pub mod firework_rocket;
|
||||||
|
pub mod fishing_bobber;
|
||||||
pub mod lingering_potion;
|
pub mod lingering_potion;
|
||||||
pub mod shulker_bullet;
|
pub mod shulker_bullet;
|
||||||
pub mod small_fireball;
|
pub mod small_fireball;
|
||||||
@ -35,6 +36,7 @@ pub fn is_projectile(entity_type: &EntityType) -> bool {
|
|||||||
|| *entity_type == EntityType::SHULKER_BULLET
|
|| *entity_type == EntityType::SHULKER_BULLET
|
||||||
|| *entity_type == EntityType::FIREBALL
|
|| *entity_type == EntityType::FIREBALL
|
||||||
|| *entity_type == EntityType::SMALL_FIREBALL
|
|| *entity_type == EntityType::SMALL_FIREBALL
|
||||||
|
|| *entity_type == EntityType::FISHING_BOBBER
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThrownItemEntity {
|
pub struct ThrownItemEntity {
|
||||||
|
|||||||
@ -134,13 +134,15 @@ impl CrossbowItem {
|
|||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
if let Some(charged) = projectiles {
|
if let Some(charged) = projectiles {
|
||||||
let mut has_multishot = false;
|
let has_multishot =
|
||||||
if let Some(enchantments) = stack.get_data_component::<EnchantmentsImpl>() {
|
stack
|
||||||
has_multishot = enchantments
|
.get_data_component::<EnchantmentsImpl>()
|
||||||
.enchantment
|
.is_some_and(|enchantments| {
|
||||||
.iter()
|
enchantments
|
||||||
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT);
|
.enchantment
|
||||||
}
|
.iter()
|
||||||
|
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
|
||||||
|
});
|
||||||
|
|
||||||
let world = player.world();
|
let world = player.world();
|
||||||
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_eye;
|
||||||
pub mod ender_pearl;
|
pub mod ender_pearl;
|
||||||
pub mod firework_rocket;
|
pub mod firework_rocket;
|
||||||
|
pub mod fishing_rod;
|
||||||
pub mod glowing_ink_sac;
|
pub mod glowing_ink_sac;
|
||||||
pub mod hoe;
|
pub mod hoe;
|
||||||
pub mod honeycomb;
|
pub mod honeycomb;
|
||||||
@ -31,12 +32,14 @@ pub mod wind_charge;
|
|||||||
use crate::item::items::armor_stand::ArmorStandItem;
|
use crate::item::items::armor_stand::ArmorStandItem;
|
||||||
use crate::item::items::boat::BoatItem;
|
use crate::item::items::boat::BoatItem;
|
||||||
use crate::item::items::end_crystal::EndCrystalItem;
|
use crate::item::items::end_crystal::EndCrystalItem;
|
||||||
use crate::item::items::firework_rocket::FireworkRocketItem;
|
|
||||||
use crate::item::items::map::MapItem;
|
use crate::item::items::map::MapItem;
|
||||||
use crate::item::items::minecart::MinecartItem;
|
use crate::item::items::minecart::MinecartItem;
|
||||||
use crate::item::items::name_tag::NameTagItem;
|
use crate::item::items::name_tag::NameTagItem;
|
||||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||||
use crate::item::items::wind_charge::WindChargeItem;
|
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 super::registry::ItemRegistry;
|
||||||
use crate::item::items::potions::{LingeringPotionItem, PotionItem, SplashPotionItem};
|
use crate::item::items::potions::{LingeringPotionItem, PotionItem, SplashPotionItem};
|
||||||
@ -49,7 +52,6 @@ use dye::DyeItem;
|
|||||||
use egg::EggItem;
|
use egg::EggItem;
|
||||||
use ender_eye::EnderEyeItem;
|
use ender_eye::EnderEyeItem;
|
||||||
use ender_pearl::EnderPearlItem;
|
use ender_pearl::EnderPearlItem;
|
||||||
use glowing_ink_sac::GlowingInkSacItem;
|
|
||||||
use hoe::HoeItem;
|
use hoe::HoeItem;
|
||||||
use honeycomb::HoneyCombItem;
|
use honeycomb::HoneyCombItem;
|
||||||
use ignite::fire_charge::FireChargeItem;
|
use ignite::fire_charge::FireChargeItem;
|
||||||
@ -76,6 +78,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
|||||||
manager.register(SwordItem);
|
manager.register(SwordItem);
|
||||||
manager.register(MaceItem);
|
manager.register(MaceItem);
|
||||||
manager.register(TridentItem);
|
manager.register(TridentItem);
|
||||||
|
manager.register(FishingRodItem);
|
||||||
// TODO: Register CrossbowItem with per-shot durability cost.
|
// TODO: Register CrossbowItem with per-shot durability cost.
|
||||||
// TODO: Register BrushItem with per-stroke durability cost.
|
// TODO: Register BrushItem with per-stroke durability cost.
|
||||||
// TODO: Register CarrotOnAStickItem and WarpedFungusOnAStickItem with boost durability costs.
|
// TODO: Register CarrotOnAStickItem and WarpedFungusOnAStickItem with boost durability costs.
|
||||||
|
|||||||
@ -881,7 +881,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
let block_entity_elapsed = block_entity_start.elapsed();
|
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).
|
// Tick the End dragon fight (only on THE_END worlds).
|
||||||
if let Some(ref fight_mutex) = self.dragon_fight {
|
if let Some(ref fight_mutex) = self.dragon_fight {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user