From f54f6aa123545eccb4622152c41a88742cd6e8a1 Mon Sep 17 00:00:00 2001 From: kralverde Date: Thu, 17 Oct 2024 20:19:38 -0400 Subject: [PATCH 01/14] add favicon to config --- docs/config/basic.md | 8 ++++++++ pumpkin-config/src/lib.rs | 4 ++++ pumpkin/src/server/connection_cache.rs | 5 +++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/config/basic.md b/docs/config/basic.md index 7d646c33c..f7635e547 100644 --- a/docs/config/basic.md +++ b/docs/config/basic.md @@ -100,6 +100,14 @@ The server's description displayed on the status screen. motd=true ``` +## Favicon + +The path to the server's favicon + +```toml +favicon_path= +``` + ## Default gamemode The default game mode for players diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 498218640..eeca5d973 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -97,6 +97,9 @@ pub struct BasicConfiguration { /// Whether to remove IPs from logs or not #[serde_inline_default(true)] pub scrub_ips: bool, + /// Path to server favicon + #[serde(default = "String::new")] + pub favicon_path: String, } fn default_server_address() -> SocketAddr { @@ -119,6 +122,7 @@ impl Default for BasicConfiguration { motd: "A Blazing fast Pumpkin Server!".to_string(), default_gamemode: GameMode::Survival, scrub_ips: true, + favicon_path: "".to_string(), } } } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 1a57dffed..da972d102 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -57,8 +57,9 @@ impl CachedStatus { } pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = "/icon.png"; - let icon = if Path::new(icon_path).exists() { + let icon_path = &config.favicon_path; + + let icon = if !icon_path.is_empty() && Path::new(icon_path).exists() { Some(Self::load_icon(icon_path)) } else { None From e6c6e7b10bda81d0af4a6bda90989d398bc6aea7 Mon Sep 17 00:00:00 2001 From: didli789 <20859888+didli789@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:22:07 +0300 Subject: [PATCH 02/14] /kick --- pumpkin/src/commands/cmd_kick.rs | 30 ++++++++++++++++++++++++++++++ pumpkin/src/commands/mod.rs | 2 ++ 2 files changed, 32 insertions(+) create mode 100644 pumpkin/src/commands/cmd_kick.rs diff --git a/pumpkin/src/commands/cmd_kick.rs b/pumpkin/src/commands/cmd_kick.rs new file mode 100644 index 000000000..50b8f9b3a --- /dev/null +++ b/pumpkin/src/commands/cmd_kick.rs @@ -0,0 +1,30 @@ +use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; +use crate::commands::tree::CommandTree; +use crate::commands::tree::RawArgs; +use crate::commands::tree_builder::argument; +use crate::commands::CommandSender; +use pumpkin_core::text::{color::NamedColor, TextComponent}; + +const NAMES: [&str; 1] = ["kick"]; +const DESCRIPTION: &str = "Kicks the target player from the server."; + +const ARG_TARGET: &str = "target"; + +pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option { + consume_arg_player(_src, args) +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { + let target = parse_arg_player(sender, server, ARG_TARGET, args)?; + target.kick(TextComponent::text("Kicked by an operator")); + + sender.send_message( + TextComponent::text("Player has been kicked.").color_named(NamedColor::Blue), + ); + + Ok(()) + }), + ) +} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index a0e10041f..11934c142 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -10,6 +10,7 @@ mod cmd_echest; mod cmd_gamemode; mod cmd_help; mod cmd_kill; +mod cmd_kick; mod cmd_pumpkin; mod cmd_stop; pub mod dispatcher; @@ -75,6 +76,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { dispatcher.register(cmd_help::init_command_tree()); dispatcher.register(cmd_echest::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); + dispatcher.register(cmd_kick::init_command_tree()); dispatcher } From bd1d1ebfe77a5208e2f55eb113e91e6a3219dae6 Mon Sep 17 00:00:00 2001 From: didli789 <20859888+didli789@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:38:15 +0300 Subject: [PATCH 03/14] code formatting --- pumpkin/src/commands/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 11934c142..ef6e1b230 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -9,8 +9,8 @@ mod arg_player; mod cmd_echest; mod cmd_gamemode; mod cmd_help; -mod cmd_kill; mod cmd_kick; +mod cmd_kill; mod cmd_pumpkin; mod cmd_stop; pub mod dispatcher; From 78391f20cdf0f6d45fceb2e3223cd18a5fd2c38c Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 13:47:29 +0200 Subject: [PATCH 04/14] Add Clientbound Reset Score --- pumpkin-protocol/src/client/play/c_reset_score.rs | 9 +++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 pumpkin-protocol/src/client/play/c_reset_score.rs diff --git a/pumpkin-protocol/src/client/play/c_reset_score.rs b/pumpkin-protocol/src/client/play/c_reset_score.rs new file mode 100644 index 000000000..d694e870b --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_reset_score.rs @@ -0,0 +1,9 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +#[derive(Serialize)] +#[packet(0x44)] +pub struct CResetScore { + entity_name: String, + objective_name: Option, +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 47838aed6..09f249d14 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -25,6 +25,7 @@ mod c_player_chat_message; mod c_player_info_update; mod c_player_remove; mod c_remove_entities; +mod c_reset_score; mod c_set_container_content; mod c_set_container_property; mod c_set_container_slot; @@ -70,6 +71,7 @@ pub use c_player_chat_message::*; pub use c_player_info_update::*; pub use c_player_remove::*; pub use c_remove_entities::*; +pub use c_reset_score::*; pub use c_set_container_content::*; pub use c_set_container_property::*; pub use c_set_container_slot::*; From 2a89150bfc4cda99743b426909aa5cac729b7f1e Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 13:47:52 +0200 Subject: [PATCH 05/14] Add Clientbound Display Objective --- .../src/client/play/c_display_objective.rs | 43 +++++++++++++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 + 2 files changed, 45 insertions(+) create mode 100644 pumpkin-protocol/src/client/play/c_display_objective.rs diff --git a/pumpkin-protocol/src/client/play/c_display_objective.rs b/pumpkin-protocol/src/client/play/c_display_objective.rs new file mode 100644 index 000000000..a5181c088 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_display_objective.rs @@ -0,0 +1,43 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +use crate::VarInt; + +#[derive(Serialize)] +#[packet(0x57)] +pub struct CDisplayObjective<'a> { + position: VarInt, + score_name: &'a str, +} + +impl<'a> CDisplayObjective<'a> { + pub fn new(position: DisplaySlot, score_name: &'a str) -> Self { + Self { + position: VarInt(position as i32), + score_name, + } + } +} + +#[repr(i32)] +pub enum DisplaySlot { + List, + Sidebar, + BelowName, + TeamBlack, + TeamDarkBlue, + TeamDarkGreen, + TeanDarkAqua, + TeamDarkRed, + TeamDarkPurple, + TeamGold, + TeamGray, + TeamDarkGray, + TeamBlue, + TeamGreen, + TeamAqua, + TeamRed, + TeamLightPurple, + TeamYellow, + TeamWhite, +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 09f249d14..297904773 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -7,6 +7,7 @@ mod c_change_difficulty; mod c_chunk_data; mod c_close_container; mod c_disguised_chat_message; +mod c_display_objective; mod c_entity_animation; mod c_entity_metadata; mod c_entity_status; @@ -53,6 +54,7 @@ pub use c_change_difficulty::*; pub use c_chunk_data::*; pub use c_close_container::*; pub use c_disguised_chat_message::*; +pub use c_display_objective::*; pub use c_entity_animation::*; pub use c_entity_metadata::*; pub use c_entity_status::*; From 3d03a4bbc55a0c8c0ed321ed41b526c3b3d0eb59 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 14:57:48 +0200 Subject: [PATCH 06/14] Add Clientbound Update Objectives --- .../src/client/play/c_reset_score.rs | 9 +++ .../src/client/play/c_update_objectives.rs | 80 +++++++++++++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 + 3 files changed, 91 insertions(+) create mode 100644 pumpkin-protocol/src/client/play/c_update_objectives.rs diff --git a/pumpkin-protocol/src/client/play/c_reset_score.rs b/pumpkin-protocol/src/client/play/c_reset_score.rs index d694e870b..1dcfbc8b6 100644 --- a/pumpkin-protocol/src/client/play/c_reset_score.rs +++ b/pumpkin-protocol/src/client/play/c_reset_score.rs @@ -7,3 +7,12 @@ pub struct CResetScore { entity_name: String, objective_name: Option, } + +impl CResetScore { + pub fn new(entity_name: String, objective_name: Option) -> Self { + Self { + entity_name, + objective_name, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_update_objectives.rs b/pumpkin-protocol/src/client/play/c_update_objectives.rs new file mode 100644 index 000000000..60845dc56 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_update_objectives.rs @@ -0,0 +1,80 @@ +use pumpkin_core::text::{style::Style, TextComponent}; +use pumpkin_macros::packet; + +use crate::{ClientPacket, VarInt}; + +#[packet(0x5E)] +pub struct CUpdateObjectives<'a> { + objective_name: &'a str, + mode: u8, + display_name: TextComponent<'a>, + render_type: VarInt, + number_format: Option>, +} + +impl<'a> CUpdateObjectives<'a> { + pub fn new( + objective_name: &'a str, + mode: u8, + display_name: TextComponent<'a>, + render_type: VarInt, + number_format: Option>, + ) -> Self { + Self { + objective_name, + mode, + display_name, + render_type, + number_format, + } + } +} + +impl<'a> ClientPacket for CUpdateObjectives<'a> { + fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + bytebuf.put_string(self.objective_name); + bytebuf.put_u8(self.mode); + if self.mode == 0 || self.mode == 2 { + bytebuf.put_slice(&self.display_name.encode()); + bytebuf.put_var_int(&self.render_type); + bytebuf.put_option(&self.number_format, |p, v| { + match v { + NumberFormat::Blank => { + p.put_var_int(&VarInt(0)); + } + NumberFormat::Styled(style) => { + p.put_var_int(&VarInt(1)); + // TODO + p.put_slice(&fastnbt::to_bytes(style).unwrap()); + } + NumberFormat::Fixed(text_component) => { + p.put_var_int(&VarInt(2)); + p.put_slice(&text_component.encode()); + } + } + }); + } + } +} + +#[repr(u8)] +pub enum Mode { + Add, + Remove, + Update, +} + +#[repr(i32)] +pub enum RenderType { + Integer, + Hearts, +} + +pub enum NumberFormat<'a> { + /// Show nothing + Blank, + /// The styling to be used when formatting the score number + Styled(Style<'a>), + /// The text to be used as placeholder. + Fixed(TextComponent<'a>), +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 297904773..816f4a8b7 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -42,6 +42,7 @@ mod c_unload_chunk; mod c_update_entity_pos; mod c_update_entity_pos_rot; mod c_update_entity_rot; +mod c_update_objectives; mod c_worldevent; mod player_action; @@ -89,5 +90,6 @@ pub use c_unload_chunk::*; pub use c_update_entity_pos::*; pub use c_update_entity_pos_rot::*; pub use c_update_entity_rot::*; +pub use c_update_objectives::*; pub use c_worldevent::*; pub use player_action::*; From 5d0bb14a85000d6712b5cf6b4b18ae6725fa94e3 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 15:09:32 +0200 Subject: [PATCH 07/14] Add Clientbound Update Score --- .../src/client/play/c_update_objectives.rs | 19 +++--------- .../src/client/play/c_update_score.rs | 31 +++++++++++++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 ++ pumpkin-protocol/src/lib.rs | 9 ++++++ 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_update_score.rs diff --git a/pumpkin-protocol/src/client/play/c_update_objectives.rs b/pumpkin-protocol/src/client/play/c_update_objectives.rs index 60845dc56..3b1dadbb9 100644 --- a/pumpkin-protocol/src/client/play/c_update_objectives.rs +++ b/pumpkin-protocol/src/client/play/c_update_objectives.rs @@ -1,7 +1,7 @@ use pumpkin_core::text::{style::Style, TextComponent}; use pumpkin_macros::packet; -use crate::{ClientPacket, VarInt}; +use crate::{ClientPacket, NumberFormat, VarInt}; #[packet(0x5E)] pub struct CUpdateObjectives<'a> { @@ -15,16 +15,16 @@ pub struct CUpdateObjectives<'a> { impl<'a> CUpdateObjectives<'a> { pub fn new( objective_name: &'a str, - mode: u8, + mode: Mode, display_name: TextComponent<'a>, - render_type: VarInt, + render_type: RenderType, number_format: Option>, ) -> Self { Self { objective_name, - mode, + mode: mode as u8, display_name, - render_type, + render_type: VarInt(render_type as i32), number_format, } } @@ -69,12 +69,3 @@ pub enum RenderType { Integer, Hearts, } - -pub enum NumberFormat<'a> { - /// Show nothing - Blank, - /// The styling to be used when formatting the score number - Styled(Style<'a>), - /// The text to be used as placeholder. - Fixed(TextComponent<'a>), -} diff --git a/pumpkin-protocol/src/client/play/c_update_score.rs b/pumpkin-protocol/src/client/play/c_update_score.rs new file mode 100644 index 000000000..f7ecc0d7e --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_update_score.rs @@ -0,0 +1,31 @@ +use pumpkin_core::text::TextComponent; +use pumpkin_macros::packet; + +use crate::{NumberFormat, VarInt}; + +#[packet(0x61)] +pub struct CUpdateScore<'a> { + entity_name: &'a str, + objective_name: &'a str, + value: VarInt, + display_name: Option>, + number_format: Option>, +} + +impl<'a> CUpdateScore<'a> { + pub fn new( + entity_name: &'a str, + objective_name: &'a str, + value: VarInt, + display_name: Option>, + number_format: Option>, + ) -> Self { + Self { + entity_name, + objective_name, + value, + display_name, + number_format, + } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 816f4a8b7..9ce3111dc 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -43,6 +43,7 @@ mod c_update_entity_pos; mod c_update_entity_pos_rot; mod c_update_entity_rot; mod c_update_objectives; +mod c_update_score; mod c_worldevent; mod player_action; @@ -91,5 +92,6 @@ pub use c_update_entity_pos::*; pub use c_update_entity_pos_rot::*; pub use c_update_entity_rot::*; pub use c_update_objectives::*; +pub use c_update_score::*; pub use c_worldevent::*; pub use player_action::*; diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index f51cb750f..7b498490e 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -244,3 +244,12 @@ pub struct KnownPack<'a> { pub id: &'a str, pub version: &'a str, } + +pub enum NumberFormat<'a> { + /// Show nothing + Blank, + /// The styling to be used when formatting the score number + Styled(Style<'a>), + /// The text to be used as placeholder. + Fixed(TextComponent<'a>), +} From 406db91f0615b32e4abc883d4524496e584585ab Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 18:22:24 +0200 Subject: [PATCH 08/14] Add Scoreboard support --- docs/troubleshooting/common_issues.md | 2 +- .../src/client/play/c_update_objectives.rs | 2 +- .../src/client/play/c_update_score.rs | 2 + pumpkin-protocol/src/lib.rs | 2 + pumpkin/src/world/mod.rs | 5 + pumpkin/src/world/scoreboard.rs | 123 ++++++++++++++++++ 6 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 pumpkin/src/world/scoreboard.rs diff --git a/docs/troubleshooting/common_issues.md b/docs/troubleshooting/common_issues.md index 57c513a48..98786f7fa 100644 --- a/docs/troubleshooting/common_issues.md +++ b/docs/troubleshooting/common_issues.md @@ -18,7 +18,7 @@ **Cause:** The server is currently not calculating hit boxes for blocks, we're working on that. -3. The Server is unresponsive +3. ### The Server is unresponsive **Issue:** You have to wait before reconnecting or can't do basic things while chunks are loading. diff --git a/pumpkin-protocol/src/client/play/c_update_objectives.rs b/pumpkin-protocol/src/client/play/c_update_objectives.rs index 3b1dadbb9..a43500e33 100644 --- a/pumpkin-protocol/src/client/play/c_update_objectives.rs +++ b/pumpkin-protocol/src/client/play/c_update_objectives.rs @@ -1,4 +1,4 @@ -use pumpkin_core::text::{style::Style, TextComponent}; +use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; use crate::{ClientPacket, NumberFormat, VarInt}; diff --git a/pumpkin-protocol/src/client/play/c_update_score.rs b/pumpkin-protocol/src/client/play/c_update_score.rs index f7ecc0d7e..605d43b9e 100644 --- a/pumpkin-protocol/src/client/play/c_update_score.rs +++ b/pumpkin-protocol/src/client/play/c_update_score.rs @@ -1,8 +1,10 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; +use serde::Serialize; use crate::{NumberFormat, VarInt}; +#[derive(Serialize)] #[packet(0x61)] pub struct CUpdateScore<'a> { entity_name: &'a str, diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 7b498490e..1ed4cfdb8 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -1,5 +1,6 @@ use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError}; use bytes::Buf; +use pumpkin_core::text::{style::Style, TextComponent}; use serde::{Deserialize, Serialize}; use std::io::{self, Write}; use thiserror::Error; @@ -245,6 +246,7 @@ pub struct KnownPack<'a> { pub version: &'a str, } +#[derive(Serialize)] pub enum NumberFormat<'a> { /// Show nothing Blank, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 394aad40c..78e9f1c31 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -19,8 +19,11 @@ use pumpkin_protocol::{ ClientPacket, VarInt, }; use pumpkin_world::level::Level; +use scoreboard::Scoreboard; use tokio::sync::mpsc; +pub mod scoreboard; + /// Represents a Minecraft world, containing entities, players, and the underlying level data. /// /// Each dimension (Overworld, Nether, End) typically has its own `World`. @@ -35,6 +38,7 @@ pub struct World { pub level: Arc>, /// A map of active players within the world, keyed by their unique token. pub current_players: Arc>>>, + pub scoreboard: Mutex, // TODO: entities } @@ -43,6 +47,7 @@ impl World { Self { level: Arc::new(Mutex::new(level)), current_players: Arc::new(Mutex::new(HashMap::new())), + scoreboard: Mutex::new(Scoreboard::new()), } } diff --git a/pumpkin/src/world/scoreboard.rs b/pumpkin/src/world/scoreboard.rs new file mode 100644 index 000000000..fa758d630 --- /dev/null +++ b/pumpkin/src/world/scoreboard.rs @@ -0,0 +1,123 @@ +use std::collections::HashMap; + +use pumpkin_core::text::TextComponent; +use pumpkin_protocol::{ + client::play::{CDisplayObjective, CUpdateObjectives, CUpdateScore, RenderType}, + NumberFormat, VarInt, +}; + +use super::World; + +pub struct Scoreboard { + objectives: HashMap>, + // teams: HashMap, +} + +impl Default for Scoreboard { + fn default() -> Self { + Self::new() + } +} + +impl Scoreboard { + pub fn new() -> Self { + Self { + objectives: HashMap::new(), + } + } + + pub fn add_objective(&mut self, world: &World, objective: ScoreboardObjective) { + if self.objectives.contains_key(objective.name) { + // Maybe make this an error ? + log::warn!( + "Tried to create Objective which does already exist, {}", + &objective.name + ); + return; + } + world.broadcast_packet_all(&CUpdateObjectives::new( + objective.name, + pumpkin_protocol::client::play::Mode::Add, + objective.display_name, + objective.render_type, + objective.number_format, + )); + world.broadcast_packet_all(&CDisplayObjective::new( + pumpkin_protocol::client::play::DisplaySlot::Sidebar, + "test", + )); + } + + pub fn update_score(&self, world: &World, score: ScoreboardScore) { + if self.objectives.contains_key(score.objective_name) { + log::warn!( + "Tried to place a score into a Objective which does not exist, {}", + &score.objective_name + ); + return; + } + world.broadcast_packet_all(&CUpdateScore::new( + score.entity_name, + score.objective_name, + score.value, + score.display_name, + score.number_format, + )); + } + + // pub fn add_team(&mut self, name: String) { + // if self.teams.contains_key(&name) { + // // Maybe make this an error ? + // log::warn!("Tried to create Team which does already exist, {}", name); + // } + // } +} + +pub struct ScoreboardObjective<'a> { + name: &'a str, + display_name: TextComponent<'a>, + render_type: RenderType, + number_format: Option>, +} + +impl<'a> ScoreboardObjective<'a> { + pub const fn new( + name: &'a str, + display_name: TextComponent<'a>, + render_type: RenderType, + number_format: Option>, + ) -> Self { + Self { + name, + display_name, + render_type, + number_format, + } + } +} + +pub struct ScoreboardScore<'a> { + entity_name: &'a str, + objective_name: &'a str, + value: VarInt, + display_name: Option>, + number_format: Option>, +} + +impl<'a> ScoreboardScore<'a> { + pub const fn new( + entity_name: &'a str, + objective_name: &'a str, + value: VarInt, + display_name: Option>, + number_format: Option>, + ) -> Self { + Self { + entity_name, + objective_name, + value, + display_name, + number_format, + } + } +} From 5ac45b0ed59e8a6cb97c6de6093739fd578ddc63 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 18 Oct 2024 20:30:14 +0200 Subject: [PATCH 09/14] Support parsing players out of commands and custom error messages --- pumpkin/src/client/player_packet.rs | 10 +++-- pumpkin/src/commands/arg_player.rs | 67 ++++++++++++++++------------ pumpkin/src/commands/cmd_gamemode.rs | 30 ++++++++----- pumpkin/src/commands/cmd_help.rs | 9 +++- pumpkin/src/commands/cmd_kick.rs | 13 +++--- pumpkin/src/commands/cmd_kill.rs | 9 +--- pumpkin/src/commands/dispatcher.rs | 36 ++++++++------- pumpkin/src/commands/mod.rs | 8 ++-- pumpkin/src/commands/tree.rs | 6 ++- pumpkin/src/entity/player.rs | 4 +- 10 files changed, 109 insertions(+), 83 deletions(-) diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index ee9920f9e..cfc9526ac 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -1,4 +1,4 @@ -use std::f32::consts::PI; +use std::{f32::consts::PI, sync::Arc}; use crate::{ commands::CommandSender, @@ -216,9 +216,13 @@ impl Player { world.broadcast_packet_expect(&[self.client.id], &packet); } - pub fn handle_chat_command(&self, server: &Server, command: SChatCommand) { + pub fn handle_chat_command(self: &Arc, server: &Server, command: SChatCommand) { let dispatcher = server.command_dispatcher.clone(); - dispatcher.handle_command(&mut CommandSender::Player(self), server, &command.command); + dispatcher.handle_command( + &mut CommandSender::Player(self.clone()), + server, + &command.command, + ); if ADVANCED_CONFIG.commands.log_console { log::info!( "Player ({}): executed command /{}", diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index 24c91a7c4..316e152cc 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -1,39 +1,50 @@ +use std::sync::Arc; + use crate::commands::dispatcher::InvalidTreeError; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; use crate::commands::tree::{ConsumedArgs, RawArgs}; use crate::commands::CommandSender; -use crate::commands::CommandSender::Player; use crate::server::Server; /// todo: implement (so far only own name + @s/@p is implemented) -pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option { - let s = args.pop()?; - - match s { - "@s" if src.is_player() => Some(s.into()), - "@p" if src.is_player() => Some(s.into()), - "@r" => None, // todo: implement random player target selector - "@a" | "@e" => None, // todo: implement all players target selector - _ => { - // todo: implement any other player than sender - if let Player(player) = src { - let profile = &player.gameprofile; - if profile.name == s { - return Some(s.into()); - }; - }; - None +pub fn consume_arg_player( + src: &CommandSender, + server: &Server, + args: &mut RawArgs, +) -> Result> { + if let Some(arg) = args.pop() { + match arg { + "@s" => { + if src.is_player() { + return Ok(arg.into()); + } else { + return Err(Some("You are not a Player".into())); + } + } + "@p" if src.is_player() => return Ok(arg.into()), + "@r" => todo!(), // todo: implement random player target selector + "@a" | "@e" => todo!(), // todo: implement all players target selector + name => { + // todo: implement any other player than sender + for world in &server.worlds { + if world.get_player_by_name(name).is_some() { + return Ok(name.into()); + } + } + return Err(Some(format!("Player not found: {}", arg))); + } } } + Err(None) } /// todo: implement (so far only own name + @s/@p is implemented) -pub fn parse_arg_player<'a>( - src: &'a mut CommandSender, - _server: &Server, +pub fn parse_arg_player( + src: &mut CommandSender, + server: &Server, arg_name: &str, consumed_args: &ConsumedArgs, -) -> Result<&'a crate::entity::player::Player, InvalidTreeError> { +) -> Result, InvalidTreeError> { let s = consumed_args .get(arg_name) .ok_or(InvalidConsumptionError(None))? @@ -44,14 +55,12 @@ pub fn parse_arg_player<'a>( "@p" if src.is_player() => Ok(src.as_mut_player().unwrap()), "@r" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement random player target selector "@a" | "@e" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement all players target selector - _ => { - // todo: implement any other player than sender - if let Player(player) = src { - let profile = &player.gameprofile; - if profile.name == s { + name => { + for world in &server.worlds { + if let Some(player) = world.get_player_by_name(name) { return Ok(player); - }; - }; + } + } Err(InvalidConsumptionError(Some(s.into()))) } } diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index 484d85c0e..4fe656e84 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -14,6 +14,7 @@ use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs}; use crate::commands::tree_builder::{argument, require}; use crate::commands::CommandSender; use crate::commands::CommandSender::Player; +use crate::server::Server; const NAMES: [&str; 1] = ["gamemode"]; @@ -22,20 +23,27 @@ const DESCRIPTION: &str = "Change a player's gamemode."; const ARG_GAMEMODE: &str = "gamemode"; const ARG_TARGET: &str = "target"; -pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option { - let s = args.pop()?; - - if let Ok(id) = s.parse::() { - match GameMode::from_u8(id) { - None | Some(GameMode::Undefined) => {} - Some(_) => return Some(s.into()), +pub fn consume_arg_gamemode( + _src: &CommandSender, + _server: &Server, + args: &mut RawArgs, +) -> Result> { + if let Some(arg) = args.pop() { + if let Ok(id) = arg.parse::() { + match GameMode::from_u8(id) { + None | Some(GameMode::Undefined) => {} + Some(_) => return Ok(arg.into()), + }; }; - }; - match GameMode::from_str(s) { - Err(_) | Ok(GameMode::Undefined) => None, - Ok(_) => Some(s.into()), + match GameMode::from_str(arg) { + Err(_) | Ok(GameMode::Undefined) => { + return Err(Some(format!("Gamemode not found: {}", arg))) + } + Ok(_) => return Ok(arg.into()), + } } + Err(None) } pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result { diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs index a955f4c8c..18dddc6e7 100644 --- a/pumpkin/src/commands/cmd_help.rs +++ b/pumpkin/src/commands/cmd_help.rs @@ -3,6 +3,7 @@ use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError}; use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs}; use crate::commands::tree_builder::argument; use crate::commands::CommandSender; +use crate::server::Server; use pumpkin_core::text::TextComponent; const NAMES: [&str; 3] = ["help", "h", "?"]; @@ -11,12 +12,16 @@ const DESCRIPTION: &str = "Print a help message."; const ARG_COMMAND: &str = "command"; -fn consume_arg_command(_src: &CommandSender, _args: &mut RawArgs) -> Option { +fn consume_arg_command( + _src: &CommandSender, + _server: &Server, + _args: &mut RawArgs, +) -> Result> { // let s = args.pop()?; // dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into()) // TODO - None + Err(None) } fn parse_arg_command<'a>( diff --git a/pumpkin/src/commands/cmd_kick.rs b/pumpkin/src/commands/cmd_kick.rs index 50b8f9b3a..43a24a27a 100644 --- a/pumpkin/src/commands/cmd_kick.rs +++ b/pumpkin/src/commands/cmd_kick.rs @@ -1,22 +1,19 @@ -use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; +use crate::commands::arg_player::parse_arg_player; use crate::commands::tree::CommandTree; -use crate::commands::tree::RawArgs; use crate::commands::tree_builder::argument; -use crate::commands::CommandSender; use pumpkin_core::text::{color::NamedColor, TextComponent}; +use super::arg_player::consume_arg_player; + const NAMES: [&str; 1] = ["kick"]; const DESCRIPTION: &str = "Kicks the target player from the server."; const ARG_TARGET: &str = "target"; -pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option { - consume_arg_player(_src, args) -} - pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( - argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { + argument(ARG_TARGET, consume_arg_player).execute(&|sender, server, args| { + dbg!("aa"); let target = parse_arg_player(sender, server, ARG_TARGET, args)?; target.kick(TextComponent::text("Kicked by an operator")); diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs index 77c95f40e..cf4208652 100644 --- a/pumpkin/src/commands/cmd_kill.rs +++ b/pumpkin/src/commands/cmd_kill.rs @@ -1,8 +1,6 @@ use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; use crate::commands::tree::CommandTree; -use crate::commands::tree::RawArgs; use crate::commands::tree_builder::argument; -use crate::commands::CommandSender; use pumpkin_core::text::{color::NamedColor, TextComponent}; const NAMES: [&str; 1] = ["kill"]; @@ -10,13 +8,10 @@ const DESCRIPTION: &str = "Kills a target player."; const ARG_TARGET: &str = "target"; -pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option { - consume_arg_player(_src, args) -} - pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( - argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { + argument(ARG_TARGET, consume_arg_player).execute(&|sender, server, args| { + // TODO parse entities not only players let target = parse_arg_player(sender, server, ARG_TARGET, args)?; target.living_entity.kill(); diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs index a636e8144..889bec72c 100644 --- a/pumpkin/src/commands/dispatcher.rs +++ b/pumpkin/src/commands/dispatcher.rs @@ -58,14 +58,17 @@ impl<'a> CommandDispatcher<'a> { println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); return Err("Internal Error (See logs for details)".into()); } - Ok(is_fitting_path) => { - if is_fitting_path { - return Ok(()); + Ok(is_fitting_path) => match is_fitting_path { + Ok(_) => return Ok(()), + Err(error) => { + // Custom error message or not ? + if let Some(error) = error { + return Err(error); + } } - } + }, } } - Err(format!("Invalid Syntax. Usage: {}", tree)) } @@ -90,7 +93,7 @@ impl<'a> CommandDispatcher<'a> { path: Vec, tree: &CommandTree, mut raw_args: RawArgs, - ) -> Result { + ) -> Result>, InvalidTreeError> { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { @@ -98,36 +101,37 @@ impl<'a> CommandDispatcher<'a> { NodeType::ExecuteLeaf { run } => { return if raw_args.is_empty() { run(src, server, &parsed_args)?; - Ok(true) + Ok(Ok(())) } else { - Ok(false) + Ok(Err(None)) }; } NodeType::Literal { string, .. } => { if raw_args.pop() != Some(string) { - return Ok(false); + return Ok(Err(None)); } } NodeType::Argument { consumer: consume, name, .. - } => { - if let Some(consumed) = consume(src, &mut raw_args) { + } => match consume(src, server, &mut raw_args) { + Ok(consumed) => { parsed_args.insert(name, consumed); - } else { - return Ok(false); } - } + Err(err) => { + return Ok(Err(err)); + } + }, NodeType::Require { predicate, .. } => { if !predicate(src) { - return Ok(false); + return Ok(Err(None)); } } } } - Ok(false) + Ok(Err(None)) } /// Register a command with the dispatcher. diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index ef6e1b230..64825aeba 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use dispatcher::InvalidTreeError; use pumpkin_core::text::TextComponent; use tree::ConsumedArgs; @@ -21,7 +23,7 @@ mod tree_format; pub enum CommandSender<'a> { Rcon(&'a mut Vec), Console, - Player(&'a Player), + Player(Arc), } impl<'a> CommandSender<'a> { @@ -49,9 +51,9 @@ impl<'a> CommandSender<'a> { CommandSender::Rcon(_) => true, } } - pub fn as_mut_player(&mut self) -> Option<&Player> { + pub fn as_mut_player(&mut self) -> Option> { match self { - CommandSender::Player(player) => Some(player), + CommandSender::Player(player) => Some(player.clone()), CommandSender::Console => None, CommandSender::Rcon(_) => None, } diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index ebbe3d801..65407dbcb 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -1,5 +1,5 @@ use super::RunFunctionType; -use crate::commands::CommandSender; +use crate::{commands::CommandSender, server::Server}; use std::collections::{HashMap, VecDeque}; /// see [crate::commands::tree_builder::argument] @@ -9,7 +9,9 @@ pub type RawArgs<'a> = Vec<&'a str>; pub type ConsumedArgs<'a> = HashMap<&'a str, String>; /// see [crate::commands::tree_builder::argument] -pub type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; +/// Provide value or an Optional error message, If no Error message provided the default will be used +pub type ArgumentConsumer<'a> = + fn(&CommandSender, &Server, &mut RawArgs) -> Result>; pub struct Node<'a> { pub(crate) children: Vec, diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 865c8a58a..5ec86c950 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -274,7 +274,7 @@ impl Player { } impl Player { - pub async fn process_packets(&self, server: &Arc) { + pub async fn process_packets(self: &Arc, server: &Arc) { let mut packets = self.client.client_packets_queue.lock(); while let Some(mut packet) = packets.pop_back() { match self.handle_play_packet(server, &mut packet).await { @@ -297,7 +297,7 @@ impl Player { } pub async fn handle_play_packet( - &self, + self: &Arc, server: &Arc, packet: &mut RawPacket, ) -> Result<(), Box> { From 6b7d6249de6cad1a14ee956e713dcc2b120b2862 Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 18 Oct 2024 15:58:12 -0400 Subject: [PATCH 10/14] fix png encoding, default to ./icon.png, use built-in default --- pumpkin/icon.png => assets/default_icon.png | Bin pumpkin-macros/Cargo.toml | 4 ++ pumpkin-macros/src/icon.rs | 30 ++++++++++ pumpkin-macros/src/lib.rs | 7 +++ pumpkin/Cargo.toml | 3 +- pumpkin/src/server/connection_cache.rs | 61 +++++++++++++++----- 6 files changed, 90 insertions(+), 15 deletions(-) rename pumpkin/icon.png => assets/default_icon.png (100%) create mode 100644 pumpkin-macros/src/icon.rs diff --git a/pumpkin/icon.png b/assets/default_icon.png similarity index 100% rename from pumpkin/icon.png rename to assets/default_icon.png diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index 5dca73b94..0e351b0d7 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -13,3 +13,7 @@ syn = "2.0" serde.workspace = true itertools.workspace = true serde_json = "1.0.128" + +# icon loading +base64 = "0.22.1" +png = "0.17.14" diff --git a/pumpkin-macros/src/icon.rs b/pumpkin-macros/src/icon.rs new file mode 100644 index 000000000..73d3b6fde --- /dev/null +++ b/pumpkin-macros/src/icon.rs @@ -0,0 +1,30 @@ +use std::{io::Cursor, sync::LazyLock}; + +use base64::{engine::general_purpose, Engine as _}; +use proc_macro::TokenStream; +use quote::quote; + +// TODO: This is the same as pumpkin/src/server/connection_cache.rs, but we cannot reference that in +// this crate +fn load_icon(data: &[u8]) -> String { + let icon = png::Decoder::new(Cursor::new(data)); + let reader = icon.read_info().unwrap(); + let info = reader.info(); + assert!(info.width == 64, "Icon width must be 64"); + assert!(info.height == 64, "Icon height must be 64"); + + // Once we validate the dimensions, we can encode the image as-is + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(data, &mut result); + result +} + +static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png")); + +pub fn create_icon_impl() -> TokenStream { + let encoded_icon = load_icon(&ICON); + quote! { + #encoded_icon + } + .into() +} diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 3c27f91a2..a4fced0fa 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -40,3 +40,10 @@ pub fn blocks_enum(_item: TokenStream) -> TokenStream { pub fn block_categories_enum(_item: TokenStream) -> TokenStream { block_state::block_type_enum_impl() } + +mod icon; +#[proc_macro] +/// Creates the default server icon +pub fn create_icon(_item: TokenStream) -> TokenStream { + icon::create_icon_impl() +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 8e737ab63..7808c0df0 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] # pumpkin pumpkin-core = { path = "../pumpkin-core" } +pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-inventory = { path = "../pumpkin-inventory" } pumpkin-world = { path = "../pumpkin-world" } @@ -53,7 +54,7 @@ thiserror = "1.0" # icon loading base64 = "0.22.1" -png = "0.17.14" +png = "0.17.14" # logging simple_logger = { version = "5.0.0", features = ["threads"] } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index da972d102..03d462c72 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -1,4 +1,9 @@ -use std::{fs::File, path::Path}; +use core::error; +use std::{ + fs::File, + io::{Cursor, Read}, + path::Path, +}; use base64::{engine::general_purpose, Engine as _}; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; @@ -59,9 +64,37 @@ impl CachedStatus { pub fn build_response(config: &BasicConfiguration) -> StatusResponse { let icon_path = &config.favicon_path; - let icon = if !icon_path.is_empty() && Path::new(icon_path).exists() { - Some(Self::load_icon(icon_path)) + let icon = if icon_path.is_empty() { + // See if an icon exists at ./icon.png + let default_local_path = "./icon.png"; + if Path::new(default_local_path).exists() { + log::info!("Loading server icon from {}", default_local_path); + let maybe_icon = Self::load_icon(default_local_path); + match maybe_icon { + Ok(result) => Some(result), + Err(e) => { + log::warn!("Failed to load icon: {:?}", e); + None + } + } + } else { + log::info!("Using default server icon"); + Some(pumpkin_macros::create_icon!().to_string()) + } + } else if Path::new(icon_path).exists() { + log::info!("Loading server icon from {}", icon_path); + let maybe_icon = Self::load_icon(icon_path); + match maybe_icon { + Ok(result) => Some(result), + Err(e) => { + log::warn!("Failed to load icon: {:?}", e); + None + } + } } else { + // TODO: Add definitive option to have no icon? + // Currently can just use a bad path + log::warn!("Failed to load server icon at path {}", icon_path); None }; @@ -84,20 +117,20 @@ impl CachedStatus { } } - fn load_icon>(path: P) -> String { - let icon = png::Decoder::new(File::open(path).expect("Failed to load icon")); - let mut reader = icon.read_info().unwrap(); + fn load_icon>(path: P) -> Result> { + let mut icon_file = File::open(path).expect("Failed to load icon"); + let mut buf = Vec::new(); + icon_file.read_to_end(&mut buf)?; + + let icon = png::Decoder::new(Cursor::new(&buf)); + let reader = icon.read_info()?; let info = reader.info(); assert!(info.width == 64, "Icon width must be 64"); assert!(info.height == 64, "Icon height must be 64"); - // Allocate the output buffer. - let mut buf = vec![0; reader.output_buffer_size()]; - // Read the next frame. An APNG might contain multiple frames. - let info = reader.next_frame(&mut buf).unwrap(); - // Grab the bytes of the image. - let bytes = &buf[..info.buffer_size()]; + + // Reader consumes the image. Once we verify dimensions, we want to encode the entire raw image let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(bytes, &mut result); - result + general_purpose::STANDARD.encode_string(&buf, &mut result); + Ok(result) } } From a79f11461b8ccb08f65e0fb6624ad221fb655d91 Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 18 Oct 2024 16:25:26 -0400 Subject: [PATCH 11/14] condense icon macro --- pumpkin-macros/src/icon.rs | 30 ------------------------------ pumpkin-macros/src/lib.rs | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 33 deletions(-) delete mode 100644 pumpkin-macros/src/icon.rs diff --git a/pumpkin-macros/src/icon.rs b/pumpkin-macros/src/icon.rs deleted file mode 100644 index 73d3b6fde..000000000 --- a/pumpkin-macros/src/icon.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{io::Cursor, sync::LazyLock}; - -use base64::{engine::general_purpose, Engine as _}; -use proc_macro::TokenStream; -use quote::quote; - -// TODO: This is the same as pumpkin/src/server/connection_cache.rs, but we cannot reference that in -// this crate -fn load_icon(data: &[u8]) -> String { - let icon = png::Decoder::new(Cursor::new(data)); - let reader = icon.read_info().unwrap(); - let info = reader.info(); - assert!(info.width == 64, "Icon width must be 64"); - assert!(info.height == 64, "Icon height must be 64"); - - // Once we validate the dimensions, we can encode the image as-is - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(data, &mut result); - result -} - -static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png")); - -pub fn create_icon_impl() -> TokenStream { - let encoded_icon = load_icon(&ICON); - quote! { - #encoded_icon - } - .into() -} diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index a4fced0fa..0e775bf61 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -1,3 +1,6 @@ +use std::{io::Cursor, sync::LazyLock}; + +use base64::{engine::general_purpose, Engine as _}; use proc_macro::TokenStream; use quote::quote; @@ -41,9 +44,19 @@ pub fn block_categories_enum(_item: TokenStream) -> TokenStream { block_state::block_type_enum_impl() } -mod icon; +static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png")); #[proc_macro] -/// Creates the default server icon +/// Returns a base64 string encoding of the default server favicon pub fn create_icon(_item: TokenStream) -> TokenStream { - icon::create_icon_impl() + let icon = png::Decoder::new(Cursor::new(ICON.as_ref())); + let reader = icon.read_info().unwrap(); + let info = reader.info(); + assert!(info.width == 64, "Icon width must be 64"); + assert!(info.height == 64, "Icon height must be 64"); + + // Once we validate the dimensions, we can encode the image as-is + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(ICON.as_ref(), &mut result); + + quote! {#result}.into() } From 086e347d3a8c76eb4a4fe17a978ae50c34b2510d Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 18 Oct 2024 16:53:49 -0400 Subject: [PATCH 12/14] rework favicon selection process --- docs/config/basic.md | 12 +++- icon.png | Bin 0 -> 7601 bytes pumpkin-config/src/lib.rs | 8 ++- pumpkin-macros/Cargo.toml | 4 -- pumpkin-macros/src/lib.rs | 20 ------- pumpkin/Cargo.toml | 1 - pumpkin/src/server/connection_cache.rs | 79 +++++++++++-------------- 7 files changed, 50 insertions(+), 74 deletions(-) create mode 100644 icon.png diff --git a/docs/config/basic.md b/docs/config/basic.md index f7635e547..7eda8546c 100644 --- a/docs/config/basic.md +++ b/docs/config/basic.md @@ -100,12 +100,20 @@ The server's description displayed on the status screen. motd=true ``` -## Favicon +## Use favicon + +Whether to use a server favicon or not + +```toml +use_favicon=true +``` + +## Favicon path The path to the server's favicon ```toml -favicon_path= +favicon_path=./icon.png ``` ## Default gamemode diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..81b083f4d8ffe6a235c46ae35cabcc31d7249fcc GIT binary patch literal 7601 zcmeHKXH=6}w@&EBhS(@d38Ek*q);PBlMYe^q#9Bn0wE0uNL6VgqKH%x89@+4M3gE; z5k*8m=|$;XX(G}D?i(!QtoyBX*Q|BFfAhXMIc@Lf?DOn>c5)(3PwH-m2tq(0&~|;j z6Xrmz&MG`yz;_Ob+6z=~2{!sfef^^#9-sqAf;m87mJCq9zqQ%G>>zevEDR`oK$|7Q zDuQ6n%{mHb3;k$c2ilT9WLP>#FdJw)P^SO|1+@17bq-KS;osM)1lp{JXY>6g1p?Ut zy`QzNzOkt&3XVj;(Mo_m0wsq*C;=Z)BnpF;$DonG`rx#`EMg&zp0(}->E1jMN7Jy+)s>nk!a%vS2us1eiiDiA$7Xmii>BIHji*S2TF&1$aA zS2IeKAbFhJLHq9QfH#(5N+9R^cSbzA5PSR5^(6;$*Ih#gYgNk^D`b88!0W`gY@!L9 zseJvbjBA?ks-qlEyKW1-q7&f+47Jg;MhnhO#usAku0XfX>&qh5z5vE

o+~3&GgdRi<#2T==3_y$d zh2=4jiW-xG#kmm}qRs>t5?K{G^P~zYO2Vr`t>ujo#uP1rD@o6fMzHWZX^Hc5!ztsT zYR4feObh_vL117-nI7(BI);id|;0Rl2Fa5w%EH7tdwMZcp-^C6^rw9u6l3FG@MQWA763kEnOKS} z5{{7d@R0pogU-9Xlfx3wFeL;U1;fb` z0^(7!?Hlj#gB8Ao720pM^FfP*6v@dzwI0Vao+Q-;YYqZMFSC3z$ai9@25 z5za)EBJy_#6B-GKO04_uRRT z0%6k{JPxDdN%O!0?j(6&T?n!ivdiWKi*SsFslF-{1xNffV(N}%5CH{Ms1b?m#r$i+ zlH@_KU|?BnA{9|+BoYlg2(+?-9O^G2D*}xUL?R1wGkI>#u-t+HHUkigWyL7~usIIw z1*1hHU>Tk?OHWUCRp_s@`)k@5I8Jyh1A79?AON7h60zmaL@bR!VG!TVXTceJ;z>l` z|4W*6ctlkIdBEj*Bs#FZ@8;0=6JNV6kb8@@91<- zBEttuBWSn)JOW$+;ko$}B`Ue;D5<|o`?wNVUH}M#A%Kuo`bil22Vt^5HZ04^89#Td zBKyBMQP~vuEy)0S-^YOD1>{26U&-(XXRNgIUwnS};=i~80R8VG|A^mz>H3$hf5gB) z68<;3{-x_5G4PLs|BbHyH@YByU8e|S;9rmra9NTXd^`wTw78rNbx(jcSZ`WIRxB{G zg`#(w4g$f&Sq03zqvj0^ax?UewYkT6cp&0pDy9Vy!0pXg|AdAmvwJGp_v(SRfLgT~ zuf_L|k_oa?XRQkkF+?9cL* z3S4vCsGRZf52_-U$Rvyu*vDVVk1#A{;v!C~E2j-996Qbjl7XCYt$Pssc^<_mIkVSMR8Q7l?GCU%$ab6~kSlj>-TFcL*|P_eEY-5g$}FKH%y_ZZR-30z zuG1+)&F#%^)L#xA5i%E^MNt~glWvo(;;;g2MT+OhZpA0rK89osDLinH0|{!q;Y1N zwlqYremz!InS*LYX!ATi_w{!3-B|rz<>aBHbj=5MLwt9^Y_BmvE#eVjJeF2A1;@D6 zm_258+(zq+(-n7<2CWBzrJ#XY0_KUUJBpb?iwlu2z$QTzTP~z+(bx(N3}YYgOD+s= zUJ;VbYI!*yCw`KkLeY2)^~6Gp|0+K8Pg~KsydW_7fG`V~!_PBP2fep5`w$(dC%D&AV`! z|H-o6%helw87Cpn%2lPzV{F%U%uv5oson+Lo0D-03-y8x|{ehP&Pg z5O|Bitda%Sr`sGW0&guglr*Rr3xY0-y%s#G?cXrj`Q@|kEspwIRfS&rGcakT2_08R z{@caYvX!3=(ji-HFRRRuUX?9ZfsSv92h)ZvkM+=2x-PS?)QyxB1&>(Gi)Up5mp`0Kzm+KO@-QQpFXN&`tytT(sWrw<$-ov5znwu)XsG0ECBIq8 z8eMHi>_$H}NxL|+=nbz__QEPGx-!Qc62|#9Cj!gJI{5MFmc0`UrdPFP39`n+$(9^I z)URm58+fhbJE-FUB3uPmsi*tGYy*t-ww+o}34%ChKDmGp@Hk zWvTLo*nZ|+q)g6{^nFXkZ5<~*4_(_buEZ9}zAUY(K-ym+FO}qkGe%ue>u?TZ!wQ_| zez5iK!6VlXPel()w#AqF=M89m)IGuhI7mqNNnLS1R8A?^ZU2{puQ!SgQug#b=}*l( z1Fr9g-S(14V&p5FgOt^Oa1jpS=^u!+eR|)}#oA5KN*zCn=uLK7sg^O> zyQr{pw&lLQW4BwZ_hBwmhoo#rmGh?BARCp2Bf$cf#o|per2JNMCnH4pLxep!K2lRQUS)M>Ss@-wzz;C|4$Lwb^rfVEQnfdcx<~JD>So8}@Z22aM7Z zn!ePX*D%_q`oZHC=j?-cfkaO8_jTZeoffmh`x1^6mt5y2fUSAQJ{3rHoAb~@FMP^b z25<2vV|n^YVCv5+6dS*ewPc`kaX0Iw9~E<#(n9V)*p%O}t@~+TuZ<^lud3ugL#b*= z%DxQUyoKF!^=3oQqG%Qe)FN$?=RZ%-6J>H2z(%`fN&P-UULmLEb1q4ajmGu(LWE_| z27jz)u}`$RQYYu;%0PA^b&sB;N$CubeR*@l!yG%p!-tYnp42t(Bqs8z4n}KdF0QIh z|3L-6<%gE%YxJ7i87Z5j%XNY$lFSp+cQ;f&Ydn=aeI4`sLJpLRe)KX`KD+w(K+)lt zEXCod>kS-192h^>T=eC>8S2>O4o-3jA(gPV$sl=pTkji)RP9M8aSdjca{YESlW6`A z-wHeL?nAxje9hKeT*w~KEzzVmqrv-f0x68Zm1|IXKS3TTW%;NSK+?sC5f2TcV)o@v zn|hm{o!97vz2(d`8q>&*j*%F@a! z*=CoXugrw2n8EV~_oo}#+PcT$KJ~2g^(A}t2rtCE zYYCGGRke+X(H<%nPLD7O39gDlDq&Os*-ukI6 zPUWM*z5RNbPBFJCkKBn2J+!g*XZ^7w@;a4orT9$4$;6}g8vcNMnS~=`q#JF{`WQ&5 za|EojcXnL^cUF!>Jjc~v(v8m##iY*eF*WCAtPMPhFB_d75w{r_6<@XvaVl+;RK8DE z>i2WB;n=B_^JEKoZklgSHL%RFq9Vi z5cfbJb0}xZxP@c(@LCu5>r49$u=@2K&R+<7%rNIj!s%(wKRDBc|#&SllE<)q_q?Sb)?g%Ur%B>t7ueA>y% z-EsA^y_IVR+*_ua3sP_BS$xTADo==s&f040Ys_1Wp4eM-nu@LR?rNL-gG6BSzp(BZ zr*()cPiu5Zc;<)+j)>HqRw zZ@gA&?)I&X8R~JNm2lUtx|eJB4@|Xk7e2Gt?Qw}+Wo|~u{icfIg<`F%H}l}~ zo+j}JOiBjVmo=9kU*q5g&$^FyM&Fzqt-XQbTk5D_zq^Hvl82OP?&~zM z^M2HDar+rIvQNmTf(bnx{{lzNPQ}5I@%bm_y^$At_Z9o*Wn>ye-c$QL-%uLAJ-ns2 zRKm`IN1FI&^hj5`Z`QLp%lIsF;RodjErv&fXWz=Y_a2WwrPiI;0CJM?YQLsul%-Yn z(z(8jz41avBOaZlQcXU+vxK4h4`w{pykk;5aqMgBq_Tc3G?Odk@&{)5LC7=f?e&L}I#obE{knEaxYmb{76xmT~{6M5pVc7mp zcD}-|k~&`7r zH?pfkDFa!v@btd=n&?%hZ>z`H2BYt3>b&K{HQsnFRbAP<4e{}8;J4A>Ns`YO>%1^x zi)=CD=-PX$v4H-$l7_N6YW@~IT*QswTO0vz&bcO;D7LRKJd(stUKn877A`7%n)x~` z6rS?tdK%BSp&ZG!m_eOHJ?V-^^3s9JMm(fh|I;_8g1xhdeWqgXeprj)zx#zrZ+uR$C=GGP93`wE}l7 OkiPcG6M33v&i@aB&`^Z{ literal 0 HcmV?d00001 diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index eeca5d973..ebe9c99cf 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -97,8 +97,11 @@ pub struct BasicConfiguration { /// Whether to remove IPs from logs or not #[serde_inline_default(true)] pub scrub_ips: bool, + /// Whether to use a server favicon + #[serde_inline_default(true)] + pub use_favicon: bool, /// Path to server favicon - #[serde(default = "String::new")] + #[serde_inline_default("icon.png".to_string())] pub favicon_path: String, } @@ -122,7 +125,8 @@ impl Default for BasicConfiguration { motd: "A Blazing fast Pumpkin Server!".to_string(), default_gamemode: GameMode::Survival, scrub_ips: true, - favicon_path: "".to_string(), + use_favicon: true, + favicon_path: "icon.png".to_string(), } } } diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index 0e351b0d7..5dca73b94 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -13,7 +13,3 @@ syn = "2.0" serde.workspace = true itertools.workspace = true serde_json = "1.0.128" - -# icon loading -base64 = "0.22.1" -png = "0.17.14" diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 0e775bf61..3c27f91a2 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -1,6 +1,3 @@ -use std::{io::Cursor, sync::LazyLock}; - -use base64::{engine::general_purpose, Engine as _}; use proc_macro::TokenStream; use quote::quote; @@ -43,20 +40,3 @@ pub fn blocks_enum(_item: TokenStream) -> TokenStream { pub fn block_categories_enum(_item: TokenStream) -> TokenStream { block_state::block_type_enum_impl() } - -static ICON: LazyLock<&[u8]> = LazyLock::new(|| include_bytes!("../../assets/default_icon.png")); -#[proc_macro] -/// Returns a base64 string encoding of the default server favicon -pub fn create_icon(_item: TokenStream) -> TokenStream { - let icon = png::Decoder::new(Cursor::new(ICON.as_ref())); - let reader = icon.read_info().unwrap(); - let info = reader.info(); - assert!(info.width == 64, "Icon width must be 64"); - assert!(info.height == 64, "Icon height must be 64"); - - // Once we validate the dimensions, we can encode the image as-is - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(ICON.as_ref(), &mut result); - - quote! {#result}.into() -} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 7808c0df0..89091407b 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] # pumpkin pumpkin-core = { path = "../pumpkin-core" } -pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-inventory = { path = "../pumpkin-inventory" } pumpkin-world = { path = "../pumpkin-world" } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 03d462c72..b90a7d16b 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -3,6 +3,7 @@ use std::{ fs::File, io::{Cursor, Read}, path::Path, + sync::LazyLock, }; use base64::{engine::general_purpose, Engine as _}; @@ -14,6 +15,29 @@ use pumpkin_protocol::{ use super::CURRENT_MC_VERSION; +static DEFAULT_ICON: LazyLock<&[u8]> = + LazyLock::new(|| include_bytes!("../../../assets/default_icon.png")); + +fn load_icon_from_file>(path: P) -> Result> { + let mut icon_file = File::open(path)?; + let mut buf = Vec::new(); + icon_file.read_to_end(&mut buf)?; + load_icon_from_bytes(&buf) +} + +fn load_icon_from_bytes(png_data: &[u8]) -> Result> { + let icon = png::Decoder::new(Cursor::new(&png_data)); + let reader = icon.read_info()?; + let info = reader.info(); + assert!(info.width == 64, "Icon width must be 64"); + assert!(info.height == 64, "Icon height must be 64"); + + // Reader consumes the image. Once we verify dimensions, we want to encode the entire raw image + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(png_data, &mut result); + Ok(result) +} + pub struct CachedStatus { _status_response: StatusResponse, // We cache the json response here so we don't parse it every time someone makes a Status request. @@ -62,39 +86,21 @@ impl CachedStatus { } pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = &config.favicon_path; - - let icon = if icon_path.is_empty() { - // See if an icon exists at ./icon.png - let default_local_path = "./icon.png"; - if Path::new(default_local_path).exists() { - log::info!("Loading server icon from {}", default_local_path); - let maybe_icon = Self::load_icon(default_local_path); - match maybe_icon { - Ok(result) => Some(result), - Err(e) => { - log::warn!("Failed to load icon: {:?}", e); - None - } - } - } else { - log::info!("Using default server icon"); - Some(pumpkin_macros::create_icon!().to_string()) - } - } else if Path::new(icon_path).exists() { - log::info!("Loading server icon from {}", icon_path); - let maybe_icon = Self::load_icon(icon_path); - match maybe_icon { + let icon = if config.use_favicon { + let icon_path = &config.favicon_path; + log::info!("Loading server favicon from '{}'", icon_path); + match load_icon_from_file(icon_path).or_else(|err| { + log::warn!("Failed to load icon from '{}': {}", icon_path, err); + load_icon_from_bytes(DEFAULT_ICON.as_ref()) + }) { Ok(result) => Some(result), - Err(e) => { - log::warn!("Failed to load icon: {:?}", e); + Err(err) => { + log::warn!("Failed to load default icon: {}", err); None } } } else { - // TODO: Add definitive option to have no icon? - // Currently can just use a bad path - log::warn!("Failed to load server icon at path {}", icon_path); + log::info!("Not using a server favicon"); None }; @@ -116,21 +122,4 @@ impl CachedStatus { enforce_secure_chat: false, } } - - fn load_icon>(path: P) -> Result> { - let mut icon_file = File::open(path).expect("Failed to load icon"); - let mut buf = Vec::new(); - icon_file.read_to_end(&mut buf)?; - - let icon = png::Decoder::new(Cursor::new(&buf)); - let reader = icon.read_info()?; - let info = reader.info(); - assert!(info.width == 64, "Icon width must be 64"); - assert!(info.height == 64, "Icon height must be 64"); - - // Reader consumes the image. Once we verify dimensions, we want to encode the entire raw image - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(&buf, &mut result); - Ok(result) - } } From 037315bfd5e416f323134a27a767bf6200c43164 Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 18 Oct 2024 16:54:09 -0400 Subject: [PATCH 13/14] remove icon --- icon.png | Bin 7601 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 icon.png diff --git a/icon.png b/icon.png deleted file mode 100644 index 81b083f4d8ffe6a235c46ae35cabcc31d7249fcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7601 zcmeHKXH=6}w@&EBhS(@d38Ek*q);PBlMYe^q#9Bn0wE0uNL6VgqKH%x89@+4M3gE; z5k*8m=|$;XX(G}D?i(!QtoyBX*Q|BFfAhXMIc@Lf?DOn>c5)(3PwH-m2tq(0&~|;j z6Xrmz&MG`yz;_Ob+6z=~2{!sfef^^#9-sqAf;m87mJCq9zqQ%G>>zevEDR`oK$|7Q zDuQ6n%{mHb3;k$c2ilT9WLP>#FdJw)P^SO|1+@17bq-KS;osM)1lp{JXY>6g1p?Ut zy`QzNzOkt&3XVj;(Mo_m0wsq*C;=Z)BnpF;$DonG`rx#`EMg&zp0(}->E1jMN7Jy+)s>nk!a%vS2us1eiiDiA$7Xmii>BIHji*S2TF&1$aA zS2IeKAbFhJLHq9QfH#(5N+9R^cSbzA5PSR5^(6;$*Ih#gYgNk^D`b88!0W`gY@!L9 zseJvbjBA?ks-qlEyKW1-q7&f+47Jg;MhnhO#usAku0XfX>&qh5z5vE

o+~3&GgdRi<#2T==3_y$d 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 OkiPcG6M33v&i@aB&`^Z{ From 2945a6542e7b6ab2d22f8fc2c7c7010757c688a0 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sat, 19 Oct 2024 10:57:11 +0200 Subject: [PATCH 14/14] Fix: Gamemode messages Do not send them to target, send them to sender --- Cargo.toml | 2 +- README.md | 3 ++- docs/about/introduction.md | 12 +++++++++++- .../src/client/play/c_system_chat_message.rs | 4 ++-- pumpkin-world/Cargo.toml | 1 - pumpkin/src/commands/cmd_gamemode.rs | 16 ++++++++-------- pumpkin/src/commands/mod.rs | 2 +- pumpkin/src/entity/player.rs | 2 +- 8 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 79767e8e3..d32f8e1a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rayon = "1.10.0" parking_lot = "0.12.3" crossbeam = "0.8.4" -uuid = { version = "1.10.0", features = ["serde", "v3", "v4"] } +uuid = { version = "1.11.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index b5f78af75..106d8426e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - **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. +- **Security**: Prioritizes security by preventing known security exploits. - **Flexibility**: Highly configurable, with the ability to disable unnecessary features. - **Extensibility**: Provides a foundation for plugin development. @@ -48,6 +48,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [x] Entity Spawning - [x] Chunk Loading - [x] Chunk Generation + - [x] Scoreboard - [ ] World Borders - [ ] World Saving - Player diff --git a/docs/about/introduction.md b/docs/about/introduction.md index 8f4c216b2..11703d96c 100644 --- a/docs/about/introduction.md +++ b/docs/about/introduction.md @@ -9,7 +9,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - **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. +- **Security**: Prioritizes security by preventing known security exploits. - **Flexibility**: Highly configurable, with the ability to disable unnecessary features. - **Extensibility**: Provides a foundation for plugin development. @@ -20,3 +20,13 @@ and customizable experience. It prioritizes performance and player enjoyment whi > [!IMPORTANT] > Pumpkin is currently under heavy development. Check out our [Github Project](https://github.com/users/Snowiiii/projects/12/views/3) to see current progress + +## Vanilla + +Pumpkin is designed to replicate vanilla Minecraft logic as closely as possible, +ensuring a familiar gameplay experience. However, we've also added new features to enhance your gameplay. + +### Redstone + +Unlike other forks that may compromise vanilla redstone mechanics, Pumpkin maintains the original redstone behavior. +If you're looking to experiment with redstone modifications or seek performance optimizations, you have the flexibility to do so through our configurable settings. diff --git a/pumpkin-protocol/src/client/play/c_system_chat_message.rs b/pumpkin-protocol/src/client/play/c_system_chat_message.rs index 2751b40c7..07c627178 100644 --- a/pumpkin-protocol/src/client/play/c_system_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_system_chat_message.rs @@ -5,12 +5,12 @@ use serde::Serialize; #[derive(Serialize)] #[packet(0x6C)] pub struct CSystemChatMessage<'a> { - content: TextComponent<'a>, + content: &'a TextComponent<'a>, overlay: bool, } impl<'a> CSystemChatMessage<'a> { - pub fn new(content: TextComponent<'a>, overlay: bool) -> Self { + pub fn new(content: &'a TextComponent<'a>, overlay: bool) -> Self { Self { content, overlay } } } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index c3eed26cb..311438a0b 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -15,7 +15,6 @@ itertools.workspace = true thiserror = "1.0" futures = "0.3" - # Compression flate2 = "1.0" lz4 = "1.11.1" diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index 4fe656e84..f86bc9771 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -74,14 +74,14 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { return if let Player(target) = sender { if target.gamemode.load() == gamemode { - target.send_system_message(TextComponent::text(&format!( + target.send_system_message(&TextComponent::text(&format!( "You already in {:?} gamemode", gamemode ))); } else { // TODO target.set_gamemode(gamemode); - target.send_system_message(TextComponent::text(&format!( + target.send_system_message(&TextComponent::text(&format!( "Game mode was set to {:?}", gamemode ))); @@ -98,16 +98,16 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { let target = parse_arg_player(sender, server, ARG_TARGET, args)?; if target.gamemode.load() == gamemode { - target.send_system_message(TextComponent::text(&format!( - "You already in {:?} gamemode", - gamemode + sender.send_message(TextComponent::text(&format!( + "{} is already in {:?} gamemode", + target.gameprofile.name, gamemode ))); } else { // TODO target.set_gamemode(gamemode); - target.send_system_message(TextComponent::text(&format!( - "Game mode was set to {:?}", - gamemode + sender.send_message(TextComponent::text(&format!( + "{}'s Game mode was set to {:?}", + target.gameprofile.name, gamemode ))); } diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 64825aeba..84ddfdd68 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -31,7 +31,7 @@ impl<'a> CommandSender<'a> { match self { // TODO: add color and stuff to console CommandSender::Console => log::info!("{}", text.to_pretty_console()), - CommandSender::Player(c) => c.send_system_message(text), + CommandSender::Player(c) => c.send_system_message(&text), CommandSender::Rcon(s) => s.push(text.to_pretty_console()), } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 5ec86c950..bb2b15ece 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -267,7 +267,7 @@ impl Player { )); } - pub fn send_system_message(&self, text: TextComponent) { + pub fn send_system_message(&self, text: &TextComponent) { self.client .send_packet(&CSystemChatMessage::new(text, false)); }