resolve merge conflicts

This commit is contained in:
user622628252416
2024-08-20 12:25:57 +02:00
53 changed files with 261 additions and 257 deletions

View File

@@ -3,7 +3,7 @@
We appreciate your interest in contributing to Pumpkin! This document outlines the guidelines for submitting bug reports, feature suggestions, and code changes.
Getting Started
The easisty way to get started is by asking for help in our [discord](https://discord.gg/wT8XjrjKkf)
The easiest way to get started is by asking for help in our [discord](https://discord.gg/wT8XjrjKkf)
### How to Contribute

View File

@@ -14,7 +14,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
![image](https://github.com/user-attachments/assets/7e2e865e-b150-4675-a2d5-b52f9900378e)
### What Pumpkin wants to achive:
### What Pumpkin wants to achieve:
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
- **Compatibility**: Supports the latest Minecraft server version and adheres to vanilla game mechanics.
- **Security**: Prioritizes security by preventing known exploits.
@@ -83,13 +83,13 @@ Make sure to generate chunks close to (0,0) since that is where the player gets
Then run:
> [!NOTE]
> This can take a while. Because we enabled heavy optimations for release builds
> This can take a while. Because we enabled heavy optimizations for release builds
```
RUSTFLAGS="-C target-cpu=native" cargo run --release
```
## Contributions
Contributions are welcome!. See [CONTRIBUTING.md](CONTRIBUTING.md)
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
## Communication
Consider joining our [discord](https://discord.gg/wT8XjrjKkf) to stay up-to-date on events, updates, and connect with other members.

View File

@@ -1,5 +1,6 @@
use pumpkin_world::item::Item;
#[allow(dead_code)]
pub struct PlayerInventory {
// Main Inventory + Hotbar
items: [Option<Item>; 36],
@@ -21,12 +22,12 @@ impl PlayerInventory {
items: [None; 36],
armor: [None; 4],
offhand: None,
// TODO: What when player spawns in with an diffrent index ?
// TODO: What when player spawns in with an different index ?
selected: 0,
}
}
pub fn set_slot(slot: u32, item: Item) {}
pub fn set_slot(_slot: u32, _item: Item) {}
pub fn set_selected(&mut self, slot: i16) {
assert!((0..9).contains(&slot));

View File

@@ -7,7 +7,7 @@ use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType};
use super::{deserializer, serializer, ByteBuffer, DeserializerError};
impl Serialize for BitSet<'static> {
impl<'a> Serialize for BitSet<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,

View File

@@ -2,23 +2,25 @@ use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use serde::Serialize;
use crate::uuid::UUID;
#[derive(Serialize)]
#[packet(0x09)]
pub struct CConfigAddResourcePack {
uuid: uuid::Uuid,
url: String,
hash: String,
pub struct CConfigAddResourcePack<'a> {
uuid: UUID,
url: &'a str,
hash: &'a str, // max 40
forced: bool,
prompt_message: Option<TextComponent>,
prompt_message: Option<TextComponent<'a>>,
}
impl CConfigAddResourcePack {
impl<'a> CConfigAddResourcePack<'a> {
pub fn new(
uuid: uuid::Uuid,
url: String,
hash: String,
uuid: UUID,
url: &'a str,
hash: &'a str,
forced: bool,
prompt_message: Option<TextComponent>,
prompt_message: Option<TextComponent<'a>>,
) -> Self {
Self {
uuid,

View File

@@ -1,8 +1,6 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bytebuf::ByteBuffer, ClientPacket};
#[derive(Serialize)]
#[packet(0x01)]
pub struct CPluginMessage<'a> {

View File

@@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x4C)]
pub struct CActionBar {
action_bar: TextComponent,
pub struct CActionBar<'a> {
action_bar: TextComponent<'a>,
}
impl CActionBar {
pub fn new(action_bar: TextComponent) -> Self {
impl<'a> CActionBar<'a> {
pub fn new(action_bar: TextComponent<'a>) -> Self {
Self { action_bar }
}
}

View File

@@ -32,7 +32,7 @@ impl<'a> ClientPacket for CChunkData<'a> {
data_buf.put_i16(block_count);
//// Block states
let palette = chunk.into_iter().dedup().collect_vec();
let palette = chunk.iter().dedup().collect_vec();
// TODO: make dynamic block_size work
// TODO: make direct block_size work
enum PaletteType {
@@ -63,7 +63,7 @@ impl<'a> ClientPacket for CChunkData<'a> {
palette.iter().enumerate().for_each(|(i, id)| {
palette_map.insert(*id, i);
// Palette
data_buf.put_var_int(&VarInt(**id as i32));
data_buf.put_var_int(&VarInt(**id));
});
for block_clump in chunk.chunks(64 / block_size as usize) {
let mut out_long: i64 = 0;
@@ -109,7 +109,7 @@ impl<'a> ClientPacket for CChunkData<'a> {
// Size
buf.put_var_int(&VarInt(data_buf.buf().len() as i32));
// Data
buf.put_slice(&data_buf.buf());
buf.put_slice(data_buf.buf());
// TODO: block entities
buf.put_var_int(&VarInt(0));

View File

@@ -6,19 +6,19 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x1E)]
pub struct CDisguisedChatMessage {
message: TextComponent,
pub struct CDisguisedChatMessage<'a> {
message: TextComponent<'a>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
}
impl CDisguisedChatMessage {
impl<'a> CDisguisedChatMessage<'a> {
pub fn new(
message: TextComponent,
message: TextComponent<'a>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
) -> Self {
Self {
message,

View File

@@ -1,7 +1,7 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::VarInt;
use crate::{position::WorldPosition, VarInt};
#[derive(Serialize)]
#[packet(0x2B)]
@@ -23,7 +23,7 @@ pub struct CLogin<'a> {
previous_gamemode: i8,
debug: bool,
is_flat: bool,
death_dimension_name: Option<(String, i64)>, // POSITION NOT STRING
death_dimension_name: Option<(WorldPosition, i64)>,
portal_cooldown: VarInt,
enforce_secure_chat: bool,
}
@@ -47,7 +47,7 @@ impl<'a> CLogin<'a> {
previous_gamemode: i8,
debug: bool,
is_flat: bool,
death_dimension_name: Option<(String, i64)>,
death_dimension_name: Option<(WorldPosition, i64)>,
portal_cooldown: VarInt,
enforce_secure_chat: bool,
) -> Self {

View File

@@ -6,14 +6,14 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x33)]
pub struct COpenScreen {
pub struct COpenScreen<'a> {
window_id: VarInt,
window_type: VarInt,
window_title: TextComponent,
window_title: TextComponent<'a>,
}
impl COpenScreen {
pub fn new(window_id: VarInt, window_type: VarInt, window_title: TextComponent) -> Self {
impl<'a> COpenScreen<'a> {
pub fn new(window_id: VarInt, window_type: VarInt, window_title: TextComponent<'a>) -> Self {
Self {
window_id,
window_type,

View File

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

View File

@@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x1D)]
pub struct CPlayDisconnect {
reason: TextComponent,
pub struct CPlayDisconnect<'a> {
reason: TextComponent<'a>,
}
impl CPlayDisconnect {
pub fn new(reason: TextComponent) -> Self {
impl<'a> CPlayDisconnect<'a> {
pub fn new(reason: TextComponent<'a>) -> Self {
Self { reason }
}
}

View File

@@ -11,19 +11,18 @@ pub struct CPlayerChatMessage<'a> {
sender: UUID,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
message: &'a str,
timestamp: i64,
salt: i64,
previous_messages_count: VarInt,
previous_messages: &'a [PreviousMessage<'a>], // max 20
unsigned_content: Option<TextComponent>,
unsigned_content: Option<TextComponent<'a>>,
/// See `FilterType`
filter_type: VarInt,
// TODO: THIS IS A HACK, We currently don't support writing or reading bitsets
filter_type_bits: bool,
filter_type_bits: Option<BitSet<'a>>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
}
impl<'a> CPlayerChatMessage<'a> {
@@ -32,15 +31,16 @@ impl<'a> CPlayerChatMessage<'a> {
sender: UUID,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
message: &'a str,
timestamp: i64,
salt: i64,
previous_messages: &'a [PreviousMessage<'a>],
unsigned_content: Option<TextComponent>,
unsigned_content: Option<TextComponent<'a>>,
filter_type: VarInt,
filter_type_bits: Option<BitSet<'a>>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
) -> Self {
Self {
sender,
@@ -53,7 +53,7 @@ impl<'a> CPlayerChatMessage<'a> {
previous_messages,
unsigned_content,
filter_type,
filter_type_bits: false,
filter_type_bits,
chat_type,
sender_name,
target_name,

View File

@@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x65)]
pub struct CTitleText {
title: TextComponent,
pub struct CTitleText<'a> {
title: TextComponent<'a>,
}
impl CTitleText {
pub fn new(title: TextComponent) -> Self {
impl<'a> CTitleText<'a> {
pub fn new(title: TextComponent<'a>) -> Self {
Self { title }
}
}

View File

@@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x63)]
pub struct CSubtitle {
subtitle: TextComponent,
pub struct CSubtitle<'a> {
subtitle: TextComponent<'a>,
}
impl CSubtitle {
pub fn new(subtitle: TextComponent) -> Self {
impl<'a> CSubtitle<'a> {
pub fn new(subtitle: TextComponent<'a>) -> Self {
Self { subtitle }
}
}

View File

@@ -4,13 +4,13 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x6C)]
pub struct CSystemChatMessge {
content: TextComponent,
pub struct CSystemChatMessge<'a> {
content: TextComponent<'a>,
overlay: bool,
}
impl CSystemChatMessge {
pub fn new(content: TextComponent, overlay: bool) -> Self {
impl<'a> CSystemChatMessge<'a> {
pub fn new(content: TextComponent<'a>, overlay: bool) -> Self {
Self { content, overlay }
}
}

View File

@@ -1,6 +1,6 @@
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
use bytes::Buf;
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use thiserror::Error;

View File

@@ -2,7 +2,7 @@ use pumpkin_macros::packet;
use crate::{
bytebuf::{ByteBuffer, DeserializerError},
ServerPacket,
ServerPacket, VarInt,
};
// derive(Deserialize)]
@@ -12,7 +12,7 @@ pub struct SChatMessage {
pub timestamp: i64,
pub salt: i64,
pub signature: Option<Vec<u8>>,
// pub messagee_count: VarInt,
pub messagee_count: VarInt,
// acknowledged: BitSet,
}
@@ -24,7 +24,7 @@ impl ServerPacket for SChatMessage {
timestamp: bytebuf.get_i64(),
salt: bytebuf.get_i64(),
signature: bytebuf.get_option(|v| v.get_slice().to_vec()),
//messagee_count: bytebuf.get_var_int(),
messagee_count: bytebuf.get_var_int(),
})
}
}

View File

@@ -1,5 +1,3 @@
use std::char::MAX;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use pumpkin_macros::packet;
@@ -22,7 +20,7 @@ impl ServerPacket for SInteract {
) -> Result<Self, crate::bytebuf::DeserializerError> {
let entity_id = bytebuf.get_var_int();
let typ = bytebuf.get_var_int();
let action = ActionType::from_i32(typ.0 as i32).unwrap();
let action = ActionType::from_i32(typ.0).unwrap();
let target_position: Option<(f32, f32, f32)> = match action {
ActionType::Interact => None,
ActionType::Attack => None,

View File

@@ -3,6 +3,7 @@ use pumpkin_macros::packet;
use crate::{position::WorldPosition, VarInt};
#[derive(serde::Deserialize)]
#[allow(dead_code)]
#[packet(0x24)]
pub struct SPlayerAction {
status: VarInt,

View File

@@ -1,5 +1,4 @@
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use pumpkin_macros::packet;
use crate::{bytebuf::DeserializerError, ServerPacket, VarInt};

View File

@@ -3,6 +3,7 @@ use pumpkin_macros::packet;
use crate::slot::Slot;
#[derive(serde::Deserialize, Debug)]
#[allow(dead_code)]
#[packet(0x32)]
pub struct SSetCreativeSlot {
slot: i16,

View File

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

View File

@@ -1,5 +1,5 @@
### Pumpkin Registry
Here you find all the registry data we have.
Registries are repositories of data that contain entries pertaining to certain aspects of the game, such as the world, the player, among others.
Registery data usally send by the Clientbound Registery data Packet.
Registry data usually send by the Clientbound Registry data Packet.
A list of Registry entries can be found at https://wiki.vg/Registry_Data

View File

@@ -11,7 +11,7 @@ pub struct ChatType {
pub struct Decoration {
translation_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<Style>,
style: Option<Style<'static>>,
parameters: Vec<String>,
}

View File

@@ -63,7 +63,7 @@ const NAMES: &[&str] = &[
"wither_skull",
];
pub(super) fn entires() -> Vec<RegistryEntry<'static>> {
pub(super) fn entries() -> Vec<RegistryEntry<'static>> {
let items: Vec<_> = NAMES
.iter()
.map(|name| RegistryEntry {

View File

@@ -64,7 +64,7 @@ impl Registry {
let damage_types = Registry {
registry_id: "minecraft:damage_type".to_string(),
registry_entries: damage_type::entires(),
registry_entries: damage_type::entries(),
};
let paintings = Registry {
registry_id: "minecraft:painting_variant".to_string(),

View File

@@ -1,19 +1,21 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
/// Action to take on click of the text.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
pub enum ClickEvent {
/// Opens an URL
OpenUrl(String),
pub enum ClickEvent<'a> {
/// Opens a URL
OpenUrl(Cow<'a, str>),
/// Works in signs, but only on the root text component
RunCommand(String),
RunCommand(Cow<'a, str>),
/// Replaces the contents of the chat box with the text, not necessarily a
/// command.
SuggestCommand(String),
SuggestCommand(Cow<'a, str>),
/// Only usable within written books. Changes the page of the book. Indexing
/// starts at 1.
ChangePage(i32),
/// Copies the given text to system clipboard
CopyToClipboard(String),
CopyToClipboard(Cow<'a, str>),
}

View File

@@ -1,3 +1,5 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::Text;
@@ -5,17 +7,17 @@ use crate::Text;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum HoverEvent {
pub enum HoverEvent<'a> {
/// Displays a tooltip with the given text.
ShowText(Text),
ShowText(Text<'a>),
/// Shows an item.
ShowItem {
/// Resource identifier of the item
id: String,
id: Cow<'a, str>,
/// Number of the items in the stack
count: Option<i32>,
/// NBT information about the item (sNBT format)
tag: String,
tag: Cow<'a, str>,
},
/// Shows an entity.
ShowEntity {
@@ -24,9 +26,9 @@ pub enum HoverEvent {
/// Resource identifier of the entity
#[serde(rename = "type")]
#[serde(default, skip_serializing_if = "Option::is_none")]
kind: Option<String>,
kind: Option<Cow<'a, str>>,
/// Optional custom name for the entity
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Text>,
name: Option<Text<'a>>,
},
}

View File

@@ -1,4 +1,5 @@
use core::str;
use std::borrow::Cow;
use click::ClickEvent;
use color::Color;
@@ -12,25 +13,34 @@ pub mod color;
pub mod hover;
pub mod style;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Text(pub Box<TextComponent>);
pub struct Text<'a>(pub Box<TextComponent<'a>>);
// Fepresents a Text component
// Represents a Text component
// Reference: https://wiki.vg/Text_formatting#Text_components
#[derive(Clone, Default, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextComponent {
pub struct TextComponent<'a> {
/// The actual text
#[serde(flatten)]
pub content: TextContent,
/// Style of the text. Bold, Italic, unterline, Color...
pub content: TextContent<'a>,
/// Style of the text. Bold, Italic, underline, Color...
/// Also has `ClickEvent
#[serde(flatten)]
pub style: Style,
pub style: Style<'a>,
}
impl serde::Serialize for TextComponent {
impl<'a> TextComponent<'a> {
pub fn text(text: &'a str) -> Self {
Self {
content: TextContent::Text { text: text.into() },
style: Style::default(),
}
}
}
impl<'a> serde::Serialize for TextComponent<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
@@ -39,7 +49,7 @@ impl serde::Serialize for TextComponent {
}
}
impl TextComponent {
impl<'a> TextComponent<'a> {
pub fn color(mut self, color: Color) -> Self {
self.style.color = Some(color);
self
@@ -87,13 +97,13 @@ impl TextComponent {
}
/// Allows for events to occur when the player clicks on text. Only work in chat.
pub fn click_event(mut self, event: ClickEvent) -> Self {
pub fn click_event(mut self, event: ClickEvent<'a>) -> Self {
self.style.click_event = Some(event);
self
}
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
pub fn hover_event(mut self, event: HoverEvent) -> Self {
pub fn hover_event(mut self, event: HoverEvent<'a>) -> Self {
self.style.hover_event = Some(event);
self
}
@@ -104,9 +114,9 @@ impl TextComponent {
#[serde(rename_all = "camelCase")]
struct TempStruct<'a> {
#[serde(flatten)]
text: &'a TextContent,
text: &'a TextContent<'a>,
#[serde(flatten)]
style: &'a Style,
style: &'a Style<'a>,
}
let astruct = TempStruct {
text: &self.content,
@@ -118,50 +128,24 @@ impl TextComponent {
}
}
impl From<String> for TextComponent {
fn from(value: String) -> Self {
Self {
content: TextContent::Text { text: value },
style: Style::default(),
}
}
}
impl From<&str> for TextComponent {
fn from(value: &str) -> Self {
Self {
content: TextContent::Text {
text: value.to_string(),
},
style: Style::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TextContent {
pub enum TextContent<'a> {
/// Raw Text
Text { text: String },
Text { text: Cow<'a, str> },
/// Translated text
Translate {
translate: String,
translate: Cow<'a, str>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
with: Vec<Text>,
with: Vec<Text<'a>>,
},
/// Displays the name of one or more entities found by a selector.
EntityNames {
selector: String,
selector: Cow<'a, str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
separator: Option<Text>,
separator: Option<Cow<'a, str>>,
},
/// A keybind identifier
/// https://minecraft.fandom.com/wiki/Controls#Configurable_controls
Keybind { keybind: String },
}
impl Default for TextContent {
fn default() -> Self {
Self::Text { text: "".into() }
}
Keybind { keybind: Cow<'a, str> },
}

View File

@@ -7,25 +7,25 @@ use crate::{
};
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct Style {
pub struct Style<'a> {
/// Changes the color to render the content
pub color: Option<Color>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bold: Option<u8>,
/// Whether to render the content in italic.
/// Keep in mind that booleans are representet as bytes in nbt
/// Keep in mind that booleans are represented as bytes in nbt
#[serde(default, skip_serializing_if = "Option::is_none")]
pub italic: Option<u8>,
/// Whether to render the content in underlined.
/// Keep in mind that booleans are representet as bytes in nbt
/// Keep in mind that booleans are represented as bytes in nbt
#[serde(default, skip_serializing_if = "Option::is_none")]
pub underlined: Option<u8>,
/// Whether to render the content in strikethrough.
/// Keep in mind that booleans are representet as bytes in nbt
/// Keep in mind that booleans are represented as bytes in nbt
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strikethrough: Option<u8>,
/// Whether to render the content in obfuscated.
/// Keep in mind that booleans are representet as bytes in nbt
/// Keep in mind that booleans are represented as bytes in nbt
#[serde(default, skip_serializing_if = "Option::is_none")]
pub obfuscated: Option<u8>,
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
@@ -33,13 +33,13 @@ pub struct Style {
pub insertion: Option<String>,
/// Allows for events to occur when the player clicks on text. Only work in chat.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub click_event: Option<ClickEvent>,
pub click_event: Option<ClickEvent<'a>>,
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hover_event: Option<HoverEvent>,
pub hover_event: Option<HoverEvent<'a>>,
}
impl Style {
impl<'a> Style<'a> {
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
@@ -87,13 +87,13 @@ impl Style {
}
/// Allows for events to occur when the player clicks on text. Only work in chat.
pub fn click_event(mut self, event: ClickEvent) -> Self {
pub fn click_event(mut self, event: ClickEvent<'a>) -> Self {
self.click_event = Some(event);
self
}
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
pub fn hover_event(mut self, event: HoverEvent) -> Self {
pub fn hover_event(mut self, event: HoverEvent<'a>) -> Self {
self.hover_event = Some(event);
self
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use lazy_static::lazy_static;
use crate::world::WorldError;
use crate::level::WorldError;
const BLOCKS_JSON: &str = include_str!("../../assets/blocks.json");

View File

@@ -1,35 +1,8 @@
// use fastnbt::nbt;
// pub const BLOCKS_AND_BIOMES: [u8; 2000] = [0x80; 2000];
// pub const SKY_LIGHT_ARRAYS: [FixedArray<u8, 2048>; 26] = [FixedArray([0xff; 2048]); 26];
// #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
// #[repr(transparent)]
// pub struct FixedArray<T, const N: usize>(pub [T; N]);
// pub struct TestChunk {
// pub heightmap: Vec<u8>,
// }
// impl Default for TestChunk {
// fn default() -> Self {
// Self::new()
// }
// }
// impl TestChunk {
// pub fn new() -> Self {
// let bytes = fastnbt::to_bytes(&nbt!({"MOTION_BLOCKING": [L; 123, 256]})).unwrap();
// Self { heightmap: bytes }
// }
// }
use std::collections::HashMap;
use fastnbt::LongArray;
use crate::{world::WorldError, WORLD_HEIGHT};
use crate::{level::WorldError, WORLD_HEIGHT};
pub struct ChunkData {
pub blocks: Box<[i32; 16 * 16 * WORLD_HEIGHT]>,
@@ -58,6 +31,7 @@ pub struct ChunkHeightmaps {
}
#[derive(serde::Deserialize, Debug)]
#[allow(dead_code)]
struct ChunkSection {
#[serde(rename = "Y")]
y: i32,
@@ -65,6 +39,7 @@ struct ChunkSection {
}
#[derive(serde::Deserialize, Debug)]
#[allow(dead_code)]
struct ChunkNbt {
#[serde(rename = "DataVersion")]
data_version: usize,

View File

@@ -1,6 +1,6 @@
use std::path::PathBuf;
use crate::world::Level;
use crate::level::Level;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dimension {

View File

@@ -26,6 +26,7 @@ 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

@@ -32,10 +32,12 @@ lazy_static! {
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.");
}
#[allow(dead_code)]
pub fn get_item_element(item_id: &str) -> &ItemComponents {
&ITEMS.get(item_id).expect("Item not found").components
}
#[allow(dead_code)]
pub fn get_item_protocol_id(item_id: &str) -> u32 {
global_registry::get_protocol_id(ITEM_REGISTRY, item_id)
}

View File

@@ -8,13 +8,12 @@ use flate2::{bufread::ZlibDecoder, read::GzDecoder};
use itertools::Itertools;
use rayon::prelude::*;
use thiserror::Error;
use tokio::{
io::{AsyncReadExt, AsyncSeekExt},
sync::mpsc,
};
use tokio::sync::mpsc;
use crate::chunk::ChunkData;
#[allow(dead_code)]
/// The Level represents a
pub struct Level {
root_folder: PathBuf,
region_folder: PathBuf,
@@ -71,8 +70,13 @@ impl Compression {
impl Level {
pub fn from_root_folder(root_folder: PathBuf) -> Self {
// TODO: Check if exists
assert!(root_folder.exists(), "World root folder does not exist!");
let region_folder = root_folder.join("region");
assert!(
region_folder.exists(),
"World region folder does not exist!"
);
Level {
root_folder,
region_folder,
@@ -81,7 +85,7 @@ impl Level {
// /// Read one chunk in the world
// ///
// /// Do not use this function if reading many chunks is required, since in case those two chunks which are read seperately using `.read_chunk` are in the same region file, it will need to be opened and closed separately for both of them, leading to a performance loss.
// /// Do not use this function if reading many chunks is required, since in case those two chunks which are read separately using `.read_chunk` are in the same region file, it will need to be opened and closed separately for both of them, leading to a performance loss.
// pub async fn read_chunk(&self, chunk: (i32, i32)) -> Result<ChunkData, WorldError> {
// self.read_chunks(vec![chunk])
// .await

View File

@@ -1,3 +1,5 @@
use level::Level;
pub mod chunk;
pub mod dimension;
pub const WORLD_HEIGHT: usize = 384;
@@ -6,6 +8,17 @@ pub const DIRECT_PALETTE_BITS: u32 = 15;
pub mod block;
mod global_registry;
pub mod item;
mod level;
pub mod radial_chunk_iterator;
pub mod vector3;
mod world;
pub struct World {
pub level: Level,
// entities, players...
}
impl World {
pub fn load(level: Level) -> Self {
Self { level }
}
}

View File

@@ -14,7 +14,7 @@ impl<T: Math + Copy> Vector3<T> {
Vector3 { x, y, z }
}
pub fn length_sqared(&self) -> T {
pub fn length_squared(&self) -> T {
self.x * self.x + self.y * self.y + self.z * self.z
}
@@ -45,7 +45,7 @@ impl<T: Math + Copy> Vector3<T> {
impl<T: Math + Copy + Float> Vector3<T> {
pub fn length(&self) -> T {
self.length_sqared().sqrt()
self.length_squared().sqrt()
}
pub fn normalize(&self) -> Self {
let length = self.length();

View File

@@ -1,5 +1,3 @@
use std::str::FromStr;
use num_traits::FromPrimitive;
use pumpkin_protocol::{
client::{
@@ -39,11 +37,15 @@ impl Client {
self.protocol_version = handshake.protocol_version.0;
self.connection_state = handshake.next_state;
if self.connection_state == ConnectionState::Login {
if self.protocol_version < CURRENT_MC_PROTOCOL as i32 {
let protocol = self.protocol_version;
self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}"));
} else if self.protocol_version > CURRENT_MC_PROTOCOL as i32 {
self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}"));
let protocol = self.protocol_version;
match protocol.cmp(&(CURRENT_MC_PROTOCOL as i32)) {
std::cmp::Ordering::Less => {
self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}"));
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}"));
}
}
}
}
@@ -200,12 +202,15 @@ impl Client {
let prompt_message = if resource_config.prompt_message.is_empty() {
None
} else {
Some(TextComponent::from(resource_config.prompt_message.clone()))
Some(TextComponent::text(&resource_config.prompt_message))
};
self.send_packet(&CConfigAddResourcePack::new(
uuid::Uuid::from_str(&resource_config.resource_pack_url).unwrap(),
resource_config.resource_pack_url.clone(),
resource_config.resource_pack_sha1.clone(),
pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3(
&uuid::Uuid::NAMESPACE_DNS,
resource_config.resource_pack_url.as_bytes(),
)),
&resource_config.resource_pack_url,
&resource_config.resource_pack_sha1,
resource_config.force,
prompt_message,
));

View File

@@ -65,7 +65,7 @@ pub struct Client {
pub protocol_version: i32,
pub connection_state: ConnectionState,
pub encrytion: bool,
pub encryption: bool,
pub closed: bool,
pub token: Rc<Token>,
pub connection: TcpStream,
@@ -89,7 +89,7 @@ impl Client {
connection,
enc: PacketEncoder::default(),
dec: PacketDecoder::default(),
encrytion: true,
encryption: true,
closed: false,
client_packets_queue: VecDeque::new(),
}
@@ -105,7 +105,7 @@ impl Client {
&mut self,
shared_secret: &[u8], // decrypted
) -> Result<(), EncryptionError> {
self.encrytion = true;
self.encryption = true;
let crypt_key: [u8; 16] = shared_secret
.try_into()
.map_err(|_| EncryptionError::SharedWrongLength)?;
@@ -375,11 +375,11 @@ impl Client {
.unwrap_or_else(|_| self.close());
}
ConnectionState::Play => {
self.try_send_packet(&CPlayDisconnect::new(TextComponent::from(reason)))
self.try_send_packet(&CPlayDisconnect::new(TextComponent::text(reason)))
.unwrap_or_else(|_| self.close());
}
_ => {
log::warn!("Cant't kick in {:?} State", self.connection_state)
log::warn!("Can't kick in {:?} State", self.connection_state)
}
}
self.close()

View File

@@ -5,7 +5,7 @@ use pumpkin_entity::EntityId;
use pumpkin_protocol::{
client::play::{
Animation, CBlockUpdate, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation,
CSystemChatMessge, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot,
CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, FilterType,
},
position::WorldPosition,
server::play::{
@@ -116,7 +116,7 @@ impl Client {
entity.x = Self::clamp_horizontal(position_rotation.x);
entity.y = Self::clamp_vertical(position_rotation.feet_y);
entity.z = Self::clamp_horizontal(position_rotation.z);
entity.yaw = wrap_degrees(position_rotation.yaw) % 360.0;
entity.yaw = wrap_degrees(position_rotation.yaw) % 360.0;
entity.pitch = wrap_degrees(position_rotation.pitch).clamp(-90.0, 90.0) % 360.0;
// send new position to all other players
@@ -209,38 +209,30 @@ impl Client {
}
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
dbg!("got message");
let message = chat_message.message;
// TODO: filter message & validation
let gameprofile = self.gameprofile.as_ref().unwrap();
dbg!("got message");
// yeah a "raw system message", the ugly way to do that, but it works
server.broadcast_packet(
self,
&CSystemChatMessge::new(
TextComponent::from(format!("{}: {}", gameprofile.name, message)),
false,
),
);
/* server.broadcast_packet(
self,
CPlayerChatMessage::new(
&CPlayerChatMessage::new(
pumpkin_protocol::uuid::UUID(gameprofile.id),
0.into(),
None,
message.clone(),
chat_message.messagee_count,
chat_message.signature.as_deref(),
&message,
chat_message.timestamp,
chat_message.salt,
&[],
Some(TextComponent::from(message.clone())),
Some(TextComponent::text(&message)),
pumpkin_protocol::VarInt(FilterType::PassThrough as i32),
0.into(),
TextComponent::from(gameprofile.name.clone()),
None,
1.into(),
TextComponent::text(&gameprofile.name.clone()),
None,
),
)
*/
/* server.broadcast_packet(
self,
&CDisguisedChatMessage::new(
@@ -272,12 +264,13 @@ impl Client {
pub fn handle_interact(&mut self, server: &mut Server, interact: SInteract) {
let action = ActionType::from_i32(interact.typ.0).unwrap();
if action == ActionType::Attack {
let attacker_player = self.player.as_ref().unwrap();
let entity_id = interact.entity_id;
// TODO: do validation and stuff
let config = &server.advanced_config.pvp;
if config.enabled {
let attacked_client = server.get_by_entityid(self, entity_id.0 as EntityId);
let attacker_player = self.player.as_mut().unwrap();
attacker_player.sneaking = interact.sneaking;
if let Some(mut client) = attacked_client {
let token = client.token.clone();
let player = client.player.as_mut().unwrap();
@@ -299,9 +292,10 @@ impl Client {
player.velocity.y as f32,
player.velocity.z as f32,
);
attacker_player.velocity = attacker_player.velocity.multiply(0.6, 1.0, 0.6);
player.velocity = velo;
client.send_packet(packet);
// attacker_player.velocity = attacker_player.velocity.multiply(0.6, 1.0, 0.6);
}
if config.hurt_animation {
// TODO
@@ -314,16 +308,14 @@ impl Client {
&CHurtAnimation::new(&entity_id, 10.0),
)
}
if config.swing {
}
if config.swing {}
} else {
self.kick("Interacted with invalid entitiy id")
}
}
}
}
pub fn handle_player_action(&mut self, _server: &mut Server, player_action: SPlayerAction) {}
pub fn handle_player_action(&mut self, _server: &mut Server, _player_action: SPlayerAction) {}
pub fn handle_use_item_on(&mut self, server: &mut Server, use_item_on: SUseItemOn) {
let location = use_item_on.location;

View File

@@ -1,6 +1,7 @@
use std::str::FromStr;
use num_traits::FromPrimitive;
use pumpkin_text::TextComponent;
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
@@ -68,7 +69,9 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
return if let Player(target) = sender {
target.set_gamemode(gamemode);
target.send_system_message(format!("Game mode was set to {:?}", gamemode).into());
target.send_system_message(TextComponent::text(
&format!("Game mode was set to {:?}", gamemode)
));
Ok(())
} else {
@@ -85,7 +88,9 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
let target = parse_arg_player(sender, ARG_TARGET, args)?;
target.set_gamemode(gamemode);
target.send_system_message(format!("Game mode was set to {:?}", gamemode).into());
target.send_system_message(TextComponent::text(
&format!("Set own game mode to {:?}", gamemode)
));
Ok(())
})

View File

@@ -1,3 +1,4 @@
use pumpkin_text::TextComponent;
use crate::commands::{CommandSender, DISPATCHER, dispatcher_init};
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
@@ -41,7 +42,9 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
let (name, tree) = parse_arg_command(args, dispatcher)?;
sender.send_message(
format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)).into()
TextComponent::text(
&format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name))
)
);
Ok(())
@@ -52,7 +55,9 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
for (name, tree) in &dispatcher.commands {
sender.send_message(
format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)).into()
TextComponent::text(
&format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name))
)
);
};

View File

@@ -12,9 +12,10 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(DESCRIPTION).execute(&|sender, _| {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");
sender.send_message(TextComponent::from(
format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green)
);
sender.send_message(TextComponent::text(
&format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")
).color_named(NamedColor::Green));
Ok(())
})

View File

@@ -89,7 +89,7 @@ pub fn handle_command(sender: &mut CommandSender, cmd: &str) {
if let Err(err) = dispatcher.dispatch(sender, cmd) {
sender.send_message(
TextComponent::from(err)
TextComponent::text(&err)
.color_named(pumpkin_text::color::NamedColor::Red),
)
}

View File

@@ -15,7 +15,7 @@ pub mod resource_pack;
const CURRENT_BASE_VERSION: &str = "1.0.0";
#[derive(Deserialize, Serialize)]
/// The idea is that Pumpkin should very customizable, You can Enable or Disable Features depning on your needs.
/// The idea is that Pumpkin should very customizable, You can Enable or Disable Features depending on your needs.
/// This also allows you get some Performance or Resource boosts.
/// Important: The Configuration should match Vanilla by default
pub struct AdvancedConfiguration {
@@ -182,7 +182,7 @@ impl AdvancedConfiguration {
if path.as_ref().exists() {
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
let config: AdvancedConfiguration =
toml::from_str(toml.as_str()).expect("Couldn't parse features.toml, Proberbly old config, Replacing with a new one or just delete it");
toml::from_str(toml.as_str()).expect("Couldn't parse features.toml, Probably old config, Replacing with a new one or just delete it");
config.validate();
config
} else {
@@ -202,7 +202,7 @@ impl BasicConfiguration {
pub fn load<P: AsRef<Path>>(path: P) -> BasicConfiguration {
if path.as_ref().exists() {
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
let config: BasicConfiguration = toml::from_str(toml.as_str()).expect("Couldn't parse configuration.toml, Proberbly old config, Replacing with a new one or just delete it");
let config: BasicConfiguration = toml::from_str(toml.as_str()).expect("Couldn't parse configuration.toml, Probably old config, Replacing with a new one or just delete it");
config.validate();
config
} else {
@@ -219,7 +219,7 @@ impl BasicConfiguration {
self.config_version, CURRENT_BASE_VERSION,
"Config version does not match used Config version. Please update your config"
);
assert!(self.view_distance >= 2, "View distance must be atleast 2");
assert!(self.view_distance >= 2, "View distance must be at least 2");
assert!(
self.view_distance <= 32,
"View distance must be less than 32"

View File

@@ -7,7 +7,7 @@ pub struct ResourcePackConfig {
pub resource_pack_url: String,
/// The SHA1 hash (40) of the resource pack.
pub resource_pack_sha1: String,
/// Custom propmt Text component, Leave blank for none
/// Custom prompt Text component, Leave blank for none
pub prompt_message: String,
/// Will force the Player to accept the resource pack
pub force: bool,

View File

@@ -1,7 +1,6 @@
use std::str::FromStr;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::Float;
use pumpkin_entity::{entity_type::EntityType, Entity, EntityId};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_protocol::VarInt;

View File

@@ -1,3 +1,5 @@
#![allow(clippy::await_holding_refcell_ref)]
use mio::net::TcpListener;
use mio::{Events, Interest, Poll, Token};
use std::io::{self};

View File

@@ -65,7 +65,7 @@ impl Packet {
buf.put_slice(bytes);
buf.put_u8(0);
buf.put_u8(0);
connection.write(&buf).unwrap();
let _ = connection.write(&buf).unwrap();
Ok(())
}

View File

@@ -3,7 +3,10 @@ use std::{
collections::HashMap,
io::Cursor,
rc::Rc,
sync::atomic::{AtomicI32, Ordering},
sync::{
atomic::{AtomicI32, Ordering},
Arc,
},
time::Duration,
};
@@ -25,12 +28,12 @@ use pumpkin_protocol::{
uuid::UUID,
ClientPacket, Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL,
};
use pumpkin_world::{dimension::Dimension, radial_chunk_iterator::RadialIterator};
use pumpkin_world::{dimension::Dimension, radial_chunk_iterator::RadialIterator, World};
use pumpkin_registry::Registry;
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tokio::sync::{mpsc, Mutex};
use crate::{
client::Client,
@@ -47,7 +50,7 @@ pub struct Server {
pub private_key: RsaPrivateKey,
pub public_key_der: Box<[u8]>,
// pub world: World,
pub world: Arc<Mutex<World>>,
pub status_response: StatusResponse,
// We cache the json response here so we don't parse it every time someone makes a Status request.
// Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves
@@ -97,11 +100,17 @@ impl Server {
None
};
log::debug!("Pumpkin does currently not have World or Chunk generation, Using ../world folder with vanilla pregenerated chunks");
let world = World::load(Dimension::OverWorld.into_level(
// TODO: load form config
"./world".parse().unwrap(),
));
Self {
cached_registry: Registry::get_static(),
// 0 is invalid
entity_id: 2.into(),
// world: World::load(""),
world: Arc::new(Mutex::new(world)),
compression_threshold: None, // 256
public_key,
cached_server_brand,
@@ -288,7 +297,8 @@ impl Server {
)
}
Server::spawn_test_chunk(client, self.base_config.view_distance as u32).await;
self.spawn_test_chunk(client, self.base_config.view_distance as u32)
.await;
}
/// TODO: This definitly should be in world
@@ -337,15 +347,15 @@ impl Server {
}
// TODO: do this in a world
async fn spawn_test_chunk(client: &mut Client, distance: u32) {
async fn spawn_test_chunk(&self, client: &mut Client, distance: u32) {
let inst = std::time::Instant::now();
let (sender, mut chunk_receiver) = mpsc::channel(distance as usize);
let world = self.world.clone();
tokio::spawn(async move {
let level = Dimension::OverWorld.into_level(
// TODO: load form config
"./world".parse().unwrap(),
);
level
world
.lock()
.await
.level
.read_chunks(RadialIterator::new(distance).collect(), sender)
.await;
});