Merge branch 'master' into master

This commit is contained in:
Edvin Bryntesson 2024-09-06 21:54:23 +02:00
commit 72d95cd416
No known key found for this signature in database
52 changed files with 653 additions and 469 deletions

538
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,17 @@
[workspace]
resolver = "2"
members = [ "pumpkin-config", "pumpkin-core", "pumpkin-entity", "pumpkin-inventory", "pumpkin-macros/", "pumpkin-plugin", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/"]
members = [
"pumpkin-config",
"pumpkin-core",
"pumpkin-entity",
"pumpkin-inventory",
"pumpkin-macros/",
"pumpkin-plugin",
"pumpkin-protocol/",
"pumpkin-registry/",
"pumpkin-world",
"pumpkin/",
]
[workspace.package]
version = "0.1.0"
@ -13,7 +24,14 @@ codegen-units = 1
[workspace.dependencies]
log = "0.4"
tokio = { version = "1.39.2", features = ["net", "macros", "rt-multi-thread", "fs", "io-util", "sync"] }
tokio = { version = "1.40", features = [
"net",
"macros",
"rt-multi-thread",
"fs",
"io-util",
"sync",
] }
rayon = "1.10.0"
uuid = { version = "1.10.0", features = ["serde", "v3"] }
derive_more = { version = "1.0.0", features = ["full"] }

View File

@ -5,8 +5,8 @@ edition.workspace = true
[dependencies]
pumpkin-core = { path = "../pumpkin-core" }
serde.workspace = true
log.workspace = true
serde = "1.0"
toml = "0.8"
log.workspace = true

View File

@ -5,8 +5,8 @@ edition.workspace = true
[dependencies]
serde.workspace = true
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
uuid.workspace = true
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
colored = "2"
md5 = "0.7.0"

View File

@ -6,7 +6,6 @@ use super::Text;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum HoverEvent<'a> {
/// Displays a tooltip with the given text.
ShowText(Text<'a>),

View File

@ -4,7 +4,4 @@ version.workspace = true
edition.workspace = true
[dependencies]
pumpkin-core = { path = "../pumpkin-core"}
num-traits = "0.2"
num-derive = "0.4"
pumpkin-core = { path = "../pumpkin-core"}

View File

@ -9,4 +9,4 @@ num-derive = "0.4"
thiserror = "1.0.63"
itertools = "0.13.0"
# For items
pumpkin-world = { path = "../pumpkin-world"}
pumpkin-world = { path = "../pumpkin-world"}

View File

@ -2,8 +2,6 @@ use crate::container_click::MouseClick;
use crate::{handle_item_change, Container, InventoryError, WindowType};
use pumpkin_world::item::ItemStack;
#[allow(dead_code)]
#[derive(Debug)]
pub struct PlayerInventory {
// Main Inventory + Hotbar
crafting: [Option<ItemStack>; 4],

View File

@ -7,6 +7,6 @@ edition.workspace = true
proc-macro = true
[dependencies]
proc-macro2 = "1.0.86"
quote = "1.0.37"
syn = "2.0.76"
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"

View File

@ -4,6 +4,6 @@ version.workspace = true
edition.workspace = true
[dependencies]
extism = "1.5.0"
log.workspace = true
serde.workspace = true
log.workspace = true
extism = "1.6.0"

View File

@ -13,12 +13,10 @@ bytes = "1.7"
uuid.workspace = true
serde.workspace = true
# to parse strings to json responses
serde_json = "1.0"
flate2 = "1.0.33"
thiserror = "1.0.63"
thiserror = "1.0"
log.workspace = true
num-traits = "0.2"
num-derive = "0.4"
@ -26,6 +24,6 @@ num-derive = "0.4"
# encryption
aes = "0.8.4"
cfb8 = "0.8.1"
take_mut = "0.2.2"
itertools = "0.13.0"
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }

View File

@ -45,77 +45,77 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<'a> {
where
V: de::Visitor<'de>,
{
visitor.visit_bool(self.inner.get_bool())
visitor.visit_bool(self.inner.get_bool()?)
}
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i8(self.inner.get_i8())
visitor.visit_i8(self.inner.get_i8()?)
}
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i16(self.inner.get_i16())
visitor.visit_i16(self.inner.get_i16()?)
}
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i32(self.inner.get_i32())
visitor.visit_i32(self.inner.get_i32()?)
}
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i64(self.inner.get_i64())
visitor.visit_i64(self.inner.get_i64()?)
}
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u8(self.inner.get_u8())
visitor.visit_u8(self.inner.get_u8()?)
}
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u16(self.inner.get_u16())
visitor.visit_u16(self.inner.get_u16()?)
}
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u32(self.inner.get_u32())
visitor.visit_u32(self.inner.get_u32()?)
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u64(self.inner.get_u64())
visitor.visit_u64(self.inner.get_u64()?)
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_f32(self.inner.get_f32())
visitor.visit_f32(self.inner.get_f32()?)
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_f64(self.inner.get_f64())
visitor.visit_f64(self.inner.get_f64()?)
}
fn deserialize_char<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
@ -129,7 +129,7 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<'a> {
where
V: de::Visitor<'de>,
{
let string = self.inner.get_string().map_err(DeserializerError::Stdio)?;
let string = self.inner.get_string()?;
visitor.visit_str(&string)
}
@ -137,7 +137,7 @@ impl<'a, 'de> de::Deserializer<'de> for Deserializer<'a> {
where
V: de::Visitor<'de>,
{
let string = self.inner.get_string().map_err(DeserializerError::Stdio)?;
let string = self.inner.get_string()?;
visitor.visit_str(&string)
}

View File

@ -1,7 +1,6 @@
use crate::{BitSet, FixedBitSet, VarInt, VarLongType};
use bytes::{Buf, BufMut, BytesMut};
use core::str;
use std::io::{self, Error, ErrorKind};
mod deserializer;
pub use deserializer::DeserializerError;
@ -26,12 +25,12 @@ impl ByteBuffer {
Self { buffer }
}
pub fn get_var_int(&mut self) -> VarInt {
pub fn get_var_int(&mut self) -> Result<VarInt, DeserializerError> {
let mut value: i32 = 0;
let mut position: i32 = 0;
loop {
let read = self.buffer.get_u8();
let read = self.get_u8()?;
value |= ((read & SEGMENT_BITS) as i32) << position;
@ -42,19 +41,19 @@ impl ByteBuffer {
position += 7;
if position >= 32 {
panic!("VarInt is too big");
return Err(DeserializerError::Message("VarInt is too big".to_string()));
}
}
VarInt(value)
Ok(VarInt(value))
}
pub fn get_var_long(&mut self) -> VarLongType {
pub fn get_var_long(&mut self) -> Result<VarLongType, DeserializerError> {
let mut value: i64 = 0;
let mut position: i64 = 0;
loop {
let read = self.buffer.get_u8();
let read = self.get_u8()?;
value |= ((read & SEGMENT_BITS) as i64) << position;
@ -65,49 +64,48 @@ impl ByteBuffer {
position += 7;
if position >= 64 {
panic!("VarInt is too big");
return Err(DeserializerError::Message("VarLong is too big".to_string()));
}
}
value
Ok(value)
}
pub fn get_string(&mut self) -> Result<String, io::Error> {
pub fn get_string(&mut self) -> Result<String, DeserializerError> {
self.get_string_len(32767)
}
pub fn get_string_len(&mut self, max_size: usize) -> Result<String, io::Error> {
let size = self.get_var_int().0;
pub fn get_string_len(&mut self, max_size: usize) -> Result<String, DeserializerError> {
let size = self.get_var_int()?.0;
if size as usize > max_size {
return Err(Error::new(
ErrorKind::InvalidData,
"String length is bigger than max size",
return Err(DeserializerError::Message(
"String length is bigger than max size".to_string(),
));
}
let data = self.buffer.copy_to_bytes(size as usize);
let data = self.copy_to_bytes(size as usize)?;
if data.len() > max_size {
return Err(Error::new(
ErrorKind::InvalidData,
"String is bigger than max size",
return Err(DeserializerError::Message(
"String is bigger than max size".to_string(),
));
}
match str::from_utf8(&data) {
Ok(string_result) => Ok(string_result.to_string()),
Err(e) => Err(Error::new(ErrorKind::InvalidData, e)),
Err(e) => Err(DeserializerError::Message(e.to_string())),
}
}
pub fn get_bool(&mut self) -> bool {
self.buffer.get_u8() != 0
pub fn get_bool(&mut self) -> Result<bool, DeserializerError> {
Ok(self.get_u8()? != 0)
}
pub fn get_uuid(&mut self) -> uuid::Uuid {
pub fn get_uuid(&mut self) -> Result<uuid::Uuid, DeserializerError> {
let mut bytes = [0u8; 16];
self.buffer.copy_to_slice(&mut bytes);
uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")
self.copy_to_slice(&mut bytes)?;
Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID"))
}
pub fn get_fixed_bitset(&mut self, bits: usize) -> FixedBitSet {
pub fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, DeserializerError> {
self.copy_to_bytes(bits.div_ceil(8))
}
@ -161,11 +159,14 @@ impl ByteBuffer {
/// Reads a boolean. If true, the closure is called, and the returned value is
/// wrapped in Some. Otherwise, this returns None.
pub fn get_option<T>(&mut self, val: impl FnOnce(&mut Self) -> T) -> Option<T> {
if self.get_bool() {
Some(val(self))
pub fn get_option<T>(
&mut self,
val: impl FnOnce(&mut Self) -> Result<T, DeserializerError>,
) -> Result<Option<T>, DeserializerError> {
if self.get_bool()? {
Ok(Some(val(self)?))
} else {
None
Ok(None)
}
}
/// Writes `true` if the option is Some, or `false` if None. If the option is
@ -177,13 +178,16 @@ impl ByteBuffer {
}
}
pub fn get_list<T>(&mut self, val: impl Fn(&mut Self) -> T) -> Vec<T> {
let len = self.get_var_int().0 as usize;
pub fn get_list<T>(
&mut self,
val: impl Fn(&mut Self) -> Result<T, DeserializerError>,
) -> Result<Vec<T>, DeserializerError> {
let len = self.get_var_int()?.0 as usize;
let mut list = Vec::with_capacity(len);
for _ in 0..len {
list.push(val(self));
list.push(val(self)?);
}
list
Ok(list)
}
/// Writes a list to the buffer.
pub fn put_list<T>(&mut self, list: &[T], write: impl Fn(&mut Self, &T)) {
@ -211,50 +215,109 @@ impl ByteBuffer {
pub fn buf(&mut self) -> &mut BytesMut {
&mut self.buffer
}
}
// trait
impl ByteBuffer {
pub fn get_u8(&mut self) -> u8 {
self.buffer.get_u8()
// Trait equivalents
pub fn get_u8(&mut self) -> Result<u8, DeserializerError> {
if self.buffer.has_remaining() {
Ok(self.buffer.get_u8())
} else {
Err(DeserializerError::Message(
"No bytes left to consume".to_string(),
))
}
}
pub fn get_i8(&mut self) -> i8 {
self.buffer.get_i8()
pub fn get_i8(&mut self) -> Result<i8, DeserializerError> {
if self.buffer.has_remaining() {
Ok(self.buffer.get_i8())
} else {
Err(DeserializerError::Message(
"No bytes left to consume".to_string(),
))
}
}
pub fn get_u16(&mut self) -> u16 {
self.buffer.get_u16()
pub fn get_u16(&mut self) -> Result<u16, DeserializerError> {
if self.buffer.remaining() >= 2 {
Ok(self.buffer.get_u16())
} else {
Err(DeserializerError::Message(
"Less than 2 bytes left to consume".to_string(),
))
}
}
pub fn get_i16(&mut self) -> i16 {
self.buffer.get_i16()
pub fn get_i16(&mut self) -> Result<i16, DeserializerError> {
if self.buffer.remaining() >= 2 {
Ok(self.buffer.get_i16())
} else {
Err(DeserializerError::Message(
"Less than 2 bytes left to consume".to_string(),
))
}
}
pub fn get_u32(&mut self) -> u32 {
self.buffer.get_u32()
pub fn get_u32(&mut self) -> Result<u32, DeserializerError> {
if self.buffer.remaining() >= 4 {
Ok(self.buffer.get_u32())
} else {
Err(DeserializerError::Message(
"Less than 4 bytes left to consume".to_string(),
))
}
}
pub fn get_i32(&mut self) -> i32 {
self.buffer.get_i32()
pub fn get_i32(&mut self) -> Result<i32, DeserializerError> {
if self.buffer.remaining() >= 4 {
Ok(self.buffer.get_i32())
} else {
Err(DeserializerError::Message(
"Less than 4 bytes left to consume".to_string(),
))
}
}
pub fn get_u64(&mut self) -> u64 {
self.buffer.get_u64()
pub fn get_u64(&mut self) -> Result<u64, DeserializerError> {
if self.buffer.remaining() >= 8 {
Ok(self.buffer.get_u64())
} else {
Err(DeserializerError::Message(
"Less than 8 bytes left to consume".to_string(),
))
}
}
pub fn get_i64(&mut self) -> i64 {
self.buffer.get_i64()
pub fn get_i64(&mut self) -> Result<i64, DeserializerError> {
if self.buffer.remaining() >= 8 {
Ok(self.buffer.get_i64())
} else {
Err(DeserializerError::Message(
"Less than 8 bytes left to consume".to_string(),
))
}
}
pub fn get_f32(&mut self) -> f32 {
self.buffer.get_f32()
pub fn get_f32(&mut self) -> Result<f32, DeserializerError> {
if self.buffer.remaining() >= 4 {
Ok(self.buffer.get_f32())
} else {
Err(DeserializerError::Message(
"Less than 4 bytes left to consume".to_string(),
))
}
}
pub fn get_f64(&mut self) -> f64 {
self.buffer.get_f64()
pub fn get_f64(&mut self) -> Result<f64, DeserializerError> {
if self.buffer.remaining() >= 8 {
Ok(self.buffer.get_f64())
} else {
Err(DeserializerError::Message(
"Less than 8 bytes left to consume".to_string(),
))
}
}
// TODO: SerializerError?
pub fn put_u8(&mut self, n: u8) {
self.buffer.put_u8(n)
}
@ -295,12 +358,25 @@ impl ByteBuffer {
self.buffer.put_f64(n)
}
pub fn copy_to_bytes(&mut self, len: usize) -> bytes::Bytes {
self.buffer.copy_to_bytes(len)
pub fn copy_to_bytes(&mut self, len: usize) -> Result<bytes::Bytes, DeserializerError> {
if self.buffer.len() >= len {
Ok(self.buffer.copy_to_bytes(len))
} else {
Err(DeserializerError::Message(
"Unable to copy bytes".to_string(),
))
}
}
pub fn copy_to_slice(&mut self, dst: &mut [u8]) {
self.buffer.copy_to_slice(dst)
pub fn copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), DeserializerError> {
if self.buffer.remaining() >= dst.len() {
self.buffer.copy_to_slice(dst);
Ok(())
} else {
Err(DeserializerError::Message(
"Unable to copy slice".to_string(),
))
}
}
pub fn put_slice(&mut self, src: &[u8]) {

View File

@ -83,12 +83,13 @@ where
P: Packet + Serialize,
{
fn write(&self, bytebuf: &mut ByteBuffer) {
take_mut::take(bytebuf, |bytebuf| {
let mut serializer = serializer::Serializer::new(bytebuf);
self.serialize(&mut serializer)
.expect("Could not serialize packet");
serializer.into()
});
let mut serializer = serializer::Serializer::new(ByteBuffer::empty());
self.serialize(&mut serializer)
.expect("Could not serialize packet");
// We write the packet in an empty bytebuffer and then put it into our current one.
// In the future we may do packet batching thats the reason i don't let every packet create a new bytebuffer and use
// an existing instead
bytebuf.put(serializer.output.buf());
}
}

View File

@ -9,7 +9,7 @@ use thiserror::Error;
use super::ByteBuffer;
pub struct Serializer {
output: ByteBuffer,
pub output: ByteBuffer,
}
impl Serializer {

View File

@ -30,7 +30,7 @@ pub struct CLogin<'a> {
}
impl<'a> CLogin<'a> {
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn new(
entity_id: i32,
is_hardcore: bool,

View File

@ -21,7 +21,7 @@ pub struct CParticle<'a> {
}
impl<'a> CParticle<'a> {
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn new(
long_distance: bool,
x: f64,

View File

@ -22,7 +22,7 @@ pub struct CPlayerChatMessage<'a> {
}
impl<'a> CPlayerChatMessage<'a> {
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn new(
sender: UUID,
index: VarInt,

View File

@ -22,7 +22,7 @@ pub struct CSpawnEntity {
}
impl CSpawnEntity {
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn new(
entity_id: VarInt,
entity_uuid: UUID,

View File

@ -22,8 +22,6 @@ pub struct PacketEncoder {
}
impl PacketEncoder {
// i think clippy is broken
#[allow(clippy::needless_borrows_for_generic_args)]
pub fn append_packet<P: ClientPacket>(&mut self, packet: &P) -> Result<(), PacketError> {
let start_len = self.buf.len();
@ -87,6 +85,7 @@ impl PacketEncoder {
let mut front = &mut self.buf[start_len..];
#[allow(clippy::needless_borrows_for_generic_args)]
VarInt(packet_len as i32)
.encode(&mut front)
.map_err(|_| PacketError::EncodeLength)?;

View File

@ -16,10 +16,10 @@ pub struct SHandShake {
impl ServerPacket for SHandShake {
fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
protocol_version: bytebuf.get_var_int(),
server_address: bytebuf.get_string_len(255).unwrap(),
server_port: bytebuf.get_u16(),
next_state: bytebuf.get_var_int().into(),
protocol_version: bytebuf.get_var_int()?,
server_address: bytebuf.get_string_len(255)?,
server_port: bytebuf.get_u16()?,
next_state: bytebuf.get_var_int()?.into(),
})
}
}

View File

@ -15,10 +15,10 @@ pub struct SEncryptionResponse {
impl ServerPacket for SEncryptionResponse {
fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
let shared_secret_length = bytebuf.get_var_int();
let shared_secret = bytebuf.copy_to_bytes(shared_secret_length.0 as usize);
let verify_token_length = bytebuf.get_var_int();
let verify_token = bytebuf.copy_to_bytes(shared_secret_length.0 as usize);
let shared_secret_length = bytebuf.get_var_int()?;
let shared_secret = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?;
let verify_token_length = bytebuf.get_var_int()?;
let verify_token = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?;
Ok(Self {
shared_secret_length,
shared_secret: shared_secret.to_vec(),

View File

@ -14,8 +14,8 @@ pub struct SLoginStart {
impl ServerPacket for SLoginStart {
fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
name: bytebuf.get_string_len(16).unwrap(),
uuid: bytebuf.get_uuid(),
name: bytebuf.get_string_len(16)?,
uuid: bytebuf.get_uuid()?,
})
}
}

View File

@ -16,9 +16,9 @@ pub struct SLoginPluginResponse {
impl ServerPacket for SLoginPluginResponse {
fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
message_id: bytebuf.get_var_int(),
successful: bytebuf.get_bool(),
data: bytebuf.get_option(|v| v.get_slice()),
message_id: bytebuf.get_var_int()?,
successful: bytebuf.get_bool()?,
data: bytebuf.get_option(|v| Ok(v.get_slice()))?,
})
}
}

View File

@ -21,12 +21,12 @@ pub struct SChatMessage {
impl ServerPacket for SChatMessage {
fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
message: bytebuf.get_string().unwrap(),
timestamp: bytebuf.get_i64(),
salt: bytebuf.get_i64(),
signature: bytebuf.get_option(|v| v.copy_to_bytes(256)),
message_count: bytebuf.get_var_int(),
acknowledged: bytebuf.get_fixed_bitset(20),
message: bytebuf.get_string()?,
timestamp: bytebuf.get_i64()?,
salt: bytebuf.get_i64()?,
signature: bytebuf.get_option(|v| v.copy_to_bytes(256))?,
message_count: bytebuf.get_var_int()?,
acknowledged: bytebuf.get_fixed_bitset(20)?,
})
}
}

View File

@ -18,8 +18,8 @@ impl ServerPacket for SInteract {
fn read(
bytebuf: &mut crate::bytebuf::ByteBuffer,
) -> Result<Self, crate::bytebuf::DeserializerError> {
let entity_id = bytebuf.get_var_int();
let typ = bytebuf.get_var_int();
let entity_id = bytebuf.get_var_int()?;
let typ = bytebuf.get_var_int()?;
let action = ActionType::from_i32(typ.0).ok_or(DeserializerError::Message(
"invalid action type".to_string(),
))?;
@ -27,13 +27,13 @@ impl ServerPacket for SInteract {
ActionType::Interact => None,
ActionType::Attack => None,
ActionType::InteractAt => {
Some((bytebuf.get_f32(), bytebuf.get_f32(), bytebuf.get_f32()))
Some((bytebuf.get_f32()?, bytebuf.get_f32()?, bytebuf.get_f32()?))
}
};
let hand = match action {
ActionType::Interact => Some(bytebuf.get_var_int()),
ActionType::Interact => Some(bytebuf.get_var_int()?),
ActionType::Attack => None,
ActionType::InteractAt => Some(bytebuf.get_var_int()),
ActionType::InteractAt => Some(bytebuf.get_var_int()?),
};
Ok(Self {
@ -41,7 +41,7 @@ impl ServerPacket for SInteract {
typ,
target_position,
hand,
sneaking: bytebuf.get_bool(),
sneaking: bytebuf.get_bool()?,
})
}
}

View File

@ -25,9 +25,9 @@ pub enum Action {
impl ServerPacket for SPlayerCommand {
fn read(bytebuf: &mut crate::bytebuf::ByteBuffer) -> Result<Self, DeserializerError> {
Ok(Self {
entity_id: bytebuf.get_var_int(),
action: bytebuf.get_var_int(),
jump_boost: bytebuf.get_var_int(),
entity_id: bytebuf.get_var_int()?,
action: bytebuf.get_var_int()?,
jump_boost: bytebuf.get_var_int()?,
})
}
}

View File

@ -7,7 +7,6 @@ use serde::{
};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Slot {
item_count: VarInt,
item_id: Option<VarInt>,

View File

@ -9,6 +9,5 @@ pumpkin-core = { path = "../pumpkin-core"}
# nbt
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
fastsnbt = "0.2"
serde.workspace = true

View File

@ -33,12 +33,12 @@ impl Registry {
registry_id: "minecraft:worldgen/biome".to_string(),
registry_entries: vec![
RegistryEntry {
entry_id: "minecraft:snowy_taiga",
entry_id: "minecraft:plains",
data: fastnbt::to_bytes_with_opts(&Biome::default(), SerOpts::network_nbt())
.unwrap(),
},
RegistryEntry {
entry_id: "minecraft:plains",
entry_id: "minecraft:snowy_taiga",
data: fastnbt::to_bytes_with_opts(&Biome::default(), SerOpts::network_nbt())
.unwrap(),
},

View File

@ -6,20 +6,22 @@ edition.workspace = true
[dependencies]
pumpkin-core = { path = "../pumpkin-core"}
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
fastsnbt = "0.2"
tokio.workspace = true
rayon.workspace = true
derive_more.workspace = true
itertools = "0.13.0"
thiserror = "1.0.63"
futures = "0.3.30"
flate2 = "1.0.33"
thiserror = "1.0"
futures = "0.3"
flate2 = "1.0"
serde.workspace = true
serde_json = "1.0"
static_assertions = "1.1.0"
log.workspace = true
noise = "0.9.0"
rand = "0.8.5"
num-traits = "0.2"
num-derive = "0.4"

View File

@ -1,9 +1,11 @@
use serde::{Deserialize, Serialize};
// TODO make this work with the protocol
// Send by the registry
#[derive(Serialize, Deserialize, Clone, Copy)]
#[non_exhaustive]
pub enum Biome {
Plains,
SnowyTiga,
// TODO list all Biomes
}

View File

@ -54,7 +54,7 @@ pub struct ChunkHeightmaps {
}
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
#[expect(dead_code)]
struct ChunkSection {
#[serde(rename = "Y")]
y: i32,
@ -64,7 +64,7 @@ struct ChunkSection {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct ChunkNbt {
#[allow(dead_code)]
#[expect(dead_code)]
data_version: usize,
#[serde(rename = "sections")]
@ -171,7 +171,7 @@ impl ChunkBlocks {
index.y.get_absolute() as usize * CHUNK_AREA + *index.z as usize * 16 + *index.x as usize
}
#[allow(dead_code)]
#[expect(dead_code)]
fn calculate_heightmap(&self) -> ChunkHeightmaps {
// figure out how LongArray is formatted
// figure out how to find out if block is motion blocking

View File

@ -24,7 +24,6 @@ pub fn get_protocol_id(category: &str, entry: &str) -> u32 {
.expect("No Entry found")
}
#[allow(dead_code)]
pub fn get_default<'a>(category: &str) -> Option<&'a str> {
REGISTRY
.get(category)

View File

@ -28,12 +28,12 @@ pub struct ItemElement {
components: ItemComponents,
}
#[allow(dead_code)]
#[expect(dead_code)]
pub fn get_item_element(item_id: &str) -> &ItemComponents {
&ITEMS.get(item_id).expect("Item not found").components
}
#[allow(dead_code)]
#[expect(dead_code)]
pub fn get_item_protocol_id(item_id: &str) -> u32 {
global_registry::get_protocol_id(ITEM_REGISTRY, item_id)
}

View File

@ -26,7 +26,7 @@ pub struct Level {
}
struct SaveFile {
#[allow(dead_code)]
#[expect(dead_code)]
root_folder: PathBuf,
region_folder: PathBuf,
}
@ -277,7 +277,7 @@ impl Level {
match compression {
Compression::Gzip => {
let mut z = GzDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new();
let mut chunk_data = Vec::with_capacity(compressed_data.len());
match z.read_to_end(&mut chunk_data) {
Ok(_) => {}
Err(e) => {
@ -288,7 +288,7 @@ impl Level {
}
Compression::Zlib => {
let mut z = ZlibDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new();
let mut chunk_data = Vec::with_capacity(compressed_data.len());
match z.read_to_end(&mut chunk_data) {
Ok(_) => {}
Err(e) => {

View File

@ -1,3 +1,4 @@
use noise::Perlin;
use pumpkin_core::math::vector2::Vector2;
use static_assertions::assert_obj_safe;
@ -12,7 +13,6 @@ pub trait GeneratorInit {
}
pub trait WorldGenerator: Sync + Send {
#[allow(dead_code)]
fn generate_chunk(&self, at: Vector2<i32>) -> ChunkData;
}
assert_obj_safe! {WorldGenerator}
@ -21,6 +21,17 @@ pub(crate) trait BiomeGenerator: Sync + Send {
fn generate_biome(&self, at: XZBlockCoordinates) -> Biome;
}
#[expect(dead_code)]
pub(crate) trait TerrainGenerator: Sync + Send {
fn prepare_chunk(&self, at: &Vector2<i32>);
/// Is static
fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockId;
}
pub(crate) trait PerlinTerrainGenerator: Sync + Send {
fn prepare_chunk(&self, at: &Vector2<i32>, perlin: &Perlin);
/// Dependens on the perlin noise height
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockId;
}

View File

@ -1,35 +1,46 @@
use noise::{NoiseFn, Perlin};
use pumpkin_core::math::vector2::Vector2;
use crate::{
chunk::{ChunkBlocks, ChunkData},
coordinates::{ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates},
WORLD_LOWEST_Y, WORLD_MAX_Y,
WORLD_LOWEST_Y,
};
use super::{
generator::{BiomeGenerator, GeneratorInit, TerrainGenerator, WorldGenerator},
generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator, WorldGenerator},
Seed,
};
pub struct GenericGenerator<B: BiomeGenerator, T: TerrainGenerator> {
pub struct GenericGenerator<B: BiomeGenerator, T: PerlinTerrainGenerator> {
biome_generator: B,
terrain_generator: T,
// TODO: May make this optional?. But would be pain to use in most biomes then. Maybe make a new trait like
// PerlinTerrainGenerator
perlin: Perlin,
}
impl<B: BiomeGenerator + GeneratorInit, T: TerrainGenerator + GeneratorInit> GeneratorInit
impl<B: BiomeGenerator + GeneratorInit, T: PerlinTerrainGenerator + GeneratorInit> GeneratorInit
for GenericGenerator<B, T>
{
fn new(seed: Seed) -> Self {
Self {
biome_generator: B::new(seed),
terrain_generator: T::new(seed),
perlin: Perlin::new(seed.0 as u32),
}
}
}
impl<B: BiomeGenerator, T: TerrainGenerator> WorldGenerator for GenericGenerator<B, T> {
impl<B: BiomeGenerator, T: PerlinTerrainGenerator> WorldGenerator for GenericGenerator<B, T> {
fn generate_chunk(&self, at: Vector2<i32>) -> ChunkData {
let mut blocks = ChunkBlocks::default();
self.terrain_generator.prepare_chunk(&at, &self.perlin);
let noise_value = self.perlin.get([at.x as f64 / 16.0, at.z as f64 / 16.0]);
let base_height = 64.0;
let height_variation = 16.0;
let chunk_height = (noise_value * height_variation + base_height) as i32;
for x in 0..16u8 {
for z in 0..16u8 {
@ -42,7 +53,7 @@ impl<B: BiomeGenerator, T: TerrainGenerator> WorldGenerator for GenericGenerator
);
// Iterate from the highest block to the lowest, in order to minimize the heightmap updates
for y in (WORLD_LOWEST_Y..WORLD_MAX_Y).rev() {
for y in (WORLD_LOWEST_Y..chunk_height as i16).rev() {
let coordinates = ChunkRelativeBlockCoordinates {
x: x.into(),
y: y.into(),
@ -51,8 +62,11 @@ impl<B: BiomeGenerator, T: TerrainGenerator> WorldGenerator for GenericGenerator
blocks.set_block(
coordinates,
self.terrain_generator
.generate_block(coordinates.with_chunk_coordinates(at), biome),
self.terrain_generator.generate_block(
coordinates.with_chunk_coordinates(at),
chunk_height as i16,
biome,
),
);
}
}
@ -64,3 +78,42 @@ impl<B: BiomeGenerator, T: TerrainGenerator> WorldGenerator for GenericGenerator
}
}
}
// TODO: implement static terrain generator
/*
fn generate_chunk(&mut self, at: Vector2<i32>) -> ChunkData {
let mut blocks = ChunkBlocks::default();
self.terrain_generator.prepare_chunk(&at, &self.perlin);
for x in 0..16u8 {
for z in 0..16u8 {
let biome = self.biome_generator.generate_biome(
ChunkRelativeXZBlockCoordinates {
x: x.into(),
z: z.into(),
}
.with_chunk_coordinates(at),
);
// Iterate from the highest block to the lowest, in order to minimize the heightmap updates
for y in (WORLD_LOWEST_Y..WORLD_MAX_Y).rev() {
let coordinates = ChunkRelativeBlockCoordinates {
x: x.into(),
y: y.into(),
z: z.into(),
};
blocks.set_block(
coordinates,
self.terrain_generator
.generate_block(coordinates.with_chunk_coordinates(at), biome),
);
}
}
}
ChunkData {
blocks,
position: at,
}
}
*/

View File

@ -0,0 +1 @@
pub mod plains;

View File

@ -0,0 +1,60 @@
use noise::Perlin;
use pumpkin_core::math::vector2::Vector2;
use crate::{
biome::Biome,
block::BlockId,
coordinates::{BlockCoordinates, XZBlockCoordinates},
world_gen::{
generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator},
generic_generator::GenericGenerator,
Seed,
},
};
pub type PlainsGenerator = GenericGenerator<PlainsBiomeGenerator, PlainsTerrainGenerator>;
pub(crate) struct PlainsBiomeGenerator {}
impl GeneratorInit for PlainsBiomeGenerator {
fn new(_: Seed) -> Self {
Self {}
}
}
impl BiomeGenerator for PlainsBiomeGenerator {
// TODO make generic over Biome and allow changing the Biome in the config.
fn generate_biome(&self, _: XZBlockCoordinates) -> Biome {
Biome::Plains
}
}
pub(crate) struct PlainsTerrainGenerator {}
impl GeneratorInit for PlainsTerrainGenerator {
fn new(_: Seed) -> Self {
Self {}
}
}
impl PerlinTerrainGenerator for PlainsTerrainGenerator {
fn prepare_chunk(&self, _at: &Vector2<i32>, _perlin: &Perlin) {}
// TODO allow specifying which blocks should be at which height in the config.
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockId {
let begin_stone_height = chunk_height - 5;
let begin_dirt_height = chunk_height - 1;
let y = *at.y;
if y == -64 {
BlockId::from_id(79) // BEDROCK
} else if y >= -63 && y <= begin_stone_height {
return BlockId::from_id(1); // STONE
} else if y >= begin_stone_height && y < begin_dirt_height {
return BlockId::from_id(10); // DIRT;
} else if y == chunk_height - 1 {
return BlockId::from_id(9); // GRASS BLOCK
} else {
BlockId::AIR
}
}
}

View File

@ -0,0 +1 @@
pub mod biome;

View File

@ -1,3 +1,5 @@
use pumpkin_core::math::vector2::Vector2;
use crate::{
biome::Biome,
block::BlockId,
@ -9,7 +11,7 @@ use crate::{
},
};
#[allow(dead_code)]
#[expect(dead_code)]
pub type SuperflatGenerator = GenericGenerator<SuperflatBiomeGenerator, SuperflatTerrainGenerator>;
pub(crate) struct SuperflatBiomeGenerator {}
@ -36,6 +38,7 @@ impl GeneratorInit for SuperflatTerrainGenerator {
}
impl TerrainGenerator for SuperflatTerrainGenerator {
fn prepare_chunk(&self, _at: &Vector2<i32>) {}
// TODO allow specifying which blocks should be at which height in the config.
fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockId {
match *at.y {

View File

@ -1,16 +1,15 @@
mod generator;
mod generic_generator;
mod implementations;
mod implementation;
mod seed;
pub use generator::WorldGenerator;
use implementation::overworld::biome::plains::PlainsGenerator;
pub use seed::Seed;
use generator::GeneratorInit;
use implementations::superflat::SuperflatGenerator;
#[allow(dead_code)]
pub fn get_world_gen(seed: Seed) -> Box<dyn WorldGenerator> {
// TODO decide which WorldGenerator to pick based on config.
Box::new(SuperflatGenerator::new(seed))
Box::new(PlainsGenerator::new(seed))
}

View File

@ -1,6 +1,5 @@
use std::hash::{DefaultHasher, Hash, Hasher};
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub struct Seed(pub i64);

View File

@ -21,7 +21,6 @@ pumpkin-registry = { path = "../pumpkin-registry"}
# config
serde.workspace = true
serde_json = "1.0"
toml = "0.8.19"
bytes = "1.7"
@ -29,7 +28,7 @@ rand = "0.8.5"
num-traits = "0.2"
num-derive = "0.4"
num-bigint = "0.4.6"
num-bigint = "0.4"
ctrlc = "3.4"
@ -47,7 +46,7 @@ digest = "=0.11.0-pre.9"
hmac = "0.12.1"
sha2 = "0.10.8"
thiserror = "1.0.63"
thiserror = "1.0"
# icon loading
base64 = "0.22.1"
@ -59,7 +58,6 @@ log.workspace = true
# networking
mio = { version = "1.0.2", features = ["os-poll", "net"]}
crossbeam-channel = "0.5.13"
uuid.workspace = true
tokio.workspace = true

View File

@ -13,8 +13,8 @@ use uuid::Uuid;
use crate::server::Server;
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
#[allow(dead_code)]
#[expect(non_snake_case)]
#[expect(dead_code)]
pub struct ProfileTextures {
timestamp: i64,
profileId: Uuid,
@ -24,8 +24,7 @@ pub struct ProfileTextures {
}
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
#[allow(dead_code)]
#[expect(dead_code)]
pub struct Texture {
url: String,
metadata: Option<HashMap<String, String>>,

View File

@ -72,7 +72,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
)));
} else {
// TODO
#[allow(clippy::let_underscore_future)]
#[expect(clippy::let_underscore_future)]
let _ = target.set_gamemode(gamemode);
target.send_system_message(TextComponent::text(&format!(
"Game mode was set to {:?}",
@ -97,7 +97,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
)));
} else {
// TODO
#[allow(clippy::let_underscore_future)]
#[expect(clippy::let_underscore_future)]
let _ = target.set_gamemode(gamemode);
target.send_system_message(TextComponent::text(&format!(
"Game mode was set to {:?}",

View File

@ -23,8 +23,9 @@ pub(crate) enum NodeType<'a> {
run: &'a (dyn Fn(&mut CommandSender, &mut Server, &ConsumedArgs) -> Result<(), InvalidTreeError>
+ Sync),
},
#[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented)
Literal { string: &'a str },
Literal {
string: &'a str,
},
Argument {
name: &'a str,
consumer: ArgumentConsumer<'a>,

View File

@ -131,7 +131,7 @@ impl<'a> NonLeafNodeBuilder<'a> {
}
/// Matches a sting literal.
#[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented)
#[expect(dead_code)] // todo: remove (so far no commands requiring this are implemented)
pub fn literal(string: &str) -> NonLeafNodeBuilder {
NonLeafNodeBuilder {
node_type: NodeType::Literal { string },

View File

@ -1,5 +1,4 @@
#![allow(clippy::await_holding_refcell_ref)]
#![allow(clippy::await_holding_lock)]
#![expect(clippy::await_holding_lock)]
#[cfg(target_os = "wasi")]
compile_error!("Compiling for WASI targets is not supported!");

View File

@ -52,7 +52,7 @@ pub fn receive_plugin_response(
buf.put_slice(data_without_signature);
// check velocity version
let version = buf.get_var_int();
let version = buf.get_var_int().unwrap();
let version = version.0;
if version > MAX_SUPPORTED_FORWARDING_VERSION {
client.kick(&format!(