Merge branch 'master' into tokio
This commit is contained in:
commit
2287564973
@ -36,7 +36,7 @@ rayon = "1.10.0"
|
|||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
crossbeam = "0.8.4"
|
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"] }
|
derive_more = { version = "1.0.0", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
|
|||||||
|
|
||||||
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
|
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
|
||||||
- **Compatibility**: Supports the latest Minecraft server version and adheres to vanilla game mechanics.
|
- **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.
|
- **Flexibility**: Highly configurable, with the ability to disable unnecessary features.
|
||||||
- **Extensibility**: Provides a foundation for plugin development.
|
- **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] Entity Spawning
|
||||||
- [x] Chunk Loading
|
- [x] Chunk Loading
|
||||||
- [x] Chunk Generation
|
- [x] Chunk Generation
|
||||||
|
- [x] Scoreboard
|
||||||
- [ ] World Borders
|
- [ ] World Borders
|
||||||
- [ ] World Saving
|
- [ ] World Saving
|
||||||
- Player
|
- Player
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@ -9,7 +9,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
|
|||||||
|
|
||||||
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
|
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
|
||||||
- **Compatibility**: Supports the latest Minecraft server version and adheres to vanilla game mechanics.
|
- **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.
|
- **Flexibility**: Highly configurable, with the ability to disable unnecessary features.
|
||||||
- **Extensibility**: Provides a foundation for plugin development.
|
- **Extensibility**: Provides a foundation for plugin development.
|
||||||
|
|
||||||
@ -20,3 +20,13 @@ and customizable experience. It prioritizes performance and player enjoyment whi
|
|||||||
|
|
||||||
> [!IMPORTANT]
|
> [!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
|
> 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.
|
||||||
|
|||||||
@ -100,6 +100,22 @@ The server's description displayed on the status screen.
|
|||||||
motd=true
|
motd=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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=./icon.png
|
||||||
|
```
|
||||||
|
|
||||||
## Default gamemode
|
## Default gamemode
|
||||||
|
|
||||||
The default game mode for players
|
The default game mode for players
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
**Cause:** The server is currently not calculating hit boxes for blocks, we're working on that.
|
**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.
|
**Issue:** You have to wait before reconnecting or can't do basic things while chunks are loading.
|
||||||
|
|
||||||
|
|||||||
@ -97,6 +97,12 @@ pub struct BasicConfiguration {
|
|||||||
/// Whether to remove IPs from logs or not
|
/// Whether to remove IPs from logs or not
|
||||||
#[serde_inline_default(true)]
|
#[serde_inline_default(true)]
|
||||||
pub scrub_ips: bool,
|
pub scrub_ips: bool,
|
||||||
|
/// Whether to use a server favicon
|
||||||
|
#[serde_inline_default(true)]
|
||||||
|
pub use_favicon: bool,
|
||||||
|
/// Path to server favicon
|
||||||
|
#[serde_inline_default("icon.png".to_string())]
|
||||||
|
pub favicon_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_server_address() -> SocketAddr {
|
fn default_server_address() -> SocketAddr {
|
||||||
@ -119,6 +125,8 @@ impl Default for BasicConfiguration {
|
|||||||
motd: "A Blazing fast Pumpkin Server!".to_string(),
|
motd: "A Blazing fast Pumpkin Server!".to_string(),
|
||||||
default_gamemode: GameMode::Survival,
|
default_gamemode: GameMode::Survival,
|
||||||
scrub_ips: true,
|
scrub_ips: true,
|
||||||
|
use_favicon: true,
|
||||||
|
favicon_path: "icon.png".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
pumpkin-protocol/src/client/play/c_display_objective.rs
Normal file
43
pumpkin-protocol/src/client/play/c_display_objective.rs
Normal file
@ -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,
|
||||||
|
}
|
||||||
18
pumpkin-protocol/src/client/play/c_reset_score.rs
Normal file
18
pumpkin-protocol/src/client/play/c_reset_score.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[packet(0x44)]
|
||||||
|
pub struct CResetScore {
|
||||||
|
entity_name: String,
|
||||||
|
objective_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CResetScore {
|
||||||
|
pub fn new(entity_name: String, objective_name: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_name,
|
||||||
|
objective_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,12 +5,12 @@ use serde::Serialize;
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[packet(0x6C)]
|
#[packet(0x6C)]
|
||||||
pub struct CSystemChatMessage<'a> {
|
pub struct CSystemChatMessage<'a> {
|
||||||
content: TextComponent<'a>,
|
content: &'a TextComponent<'a>,
|
||||||
overlay: bool,
|
overlay: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CSystemChatMessage<'a> {
|
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 }
|
Self { content, overlay }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
pumpkin-protocol/src/client/play/c_update_objectives.rs
Normal file
71
pumpkin-protocol/src/client/play/c_update_objectives.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use pumpkin_core::text::TextComponent;
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
use crate::{ClientPacket, NumberFormat, VarInt};
|
||||||
|
|
||||||
|
#[packet(0x5E)]
|
||||||
|
pub struct CUpdateObjectives<'a> {
|
||||||
|
objective_name: &'a str,
|
||||||
|
mode: u8,
|
||||||
|
display_name: TextComponent<'a>,
|
||||||
|
render_type: VarInt,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CUpdateObjectives<'a> {
|
||||||
|
pub fn new(
|
||||||
|
objective_name: &'a str,
|
||||||
|
mode: Mode,
|
||||||
|
display_name: TextComponent<'a>,
|
||||||
|
render_type: RenderType,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
objective_name,
|
||||||
|
mode: mode as u8,
|
||||||
|
display_name,
|
||||||
|
render_type: VarInt(render_type as i32),
|
||||||
|
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,
|
||||||
|
}
|
||||||
33
pumpkin-protocol/src/client/play/c_update_score.rs
Normal file
33
pumpkin-protocol/src/client/play/c_update_score.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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,
|
||||||
|
objective_name: &'a str,
|
||||||
|
value: VarInt,
|
||||||
|
display_name: Option<TextComponent<'a>>,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CUpdateScore<'a> {
|
||||||
|
pub fn new(
|
||||||
|
entity_name: &'a str,
|
||||||
|
objective_name: &'a str,
|
||||||
|
value: VarInt,
|
||||||
|
display_name: Option<TextComponent<'a>>,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_name,
|
||||||
|
objective_name,
|
||||||
|
value,
|
||||||
|
display_name,
|
||||||
|
number_format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ mod c_change_difficulty;
|
|||||||
mod c_chunk_data;
|
mod c_chunk_data;
|
||||||
mod c_close_container;
|
mod c_close_container;
|
||||||
mod c_disguised_chat_message;
|
mod c_disguised_chat_message;
|
||||||
|
mod c_display_objective;
|
||||||
mod c_entity_animation;
|
mod c_entity_animation;
|
||||||
mod c_entity_metadata;
|
mod c_entity_metadata;
|
||||||
mod c_entity_status;
|
mod c_entity_status;
|
||||||
@ -25,6 +26,7 @@ mod c_player_chat_message;
|
|||||||
mod c_player_info_update;
|
mod c_player_info_update;
|
||||||
mod c_player_remove;
|
mod c_player_remove;
|
||||||
mod c_remove_entities;
|
mod c_remove_entities;
|
||||||
|
mod c_reset_score;
|
||||||
mod c_set_container_content;
|
mod c_set_container_content;
|
||||||
mod c_set_container_property;
|
mod c_set_container_property;
|
||||||
mod c_set_container_slot;
|
mod c_set_container_slot;
|
||||||
@ -40,6 +42,8 @@ mod c_unload_chunk;
|
|||||||
mod c_update_entity_pos;
|
mod c_update_entity_pos;
|
||||||
mod c_update_entity_pos_rot;
|
mod c_update_entity_pos_rot;
|
||||||
mod c_update_entity_rot;
|
mod c_update_entity_rot;
|
||||||
|
mod c_update_objectives;
|
||||||
|
mod c_update_score;
|
||||||
mod c_worldevent;
|
mod c_worldevent;
|
||||||
mod player_action;
|
mod player_action;
|
||||||
|
|
||||||
@ -52,6 +56,7 @@ pub use c_change_difficulty::*;
|
|||||||
pub use c_chunk_data::*;
|
pub use c_chunk_data::*;
|
||||||
pub use c_close_container::*;
|
pub use c_close_container::*;
|
||||||
pub use c_disguised_chat_message::*;
|
pub use c_disguised_chat_message::*;
|
||||||
|
pub use c_display_objective::*;
|
||||||
pub use c_entity_animation::*;
|
pub use c_entity_animation::*;
|
||||||
pub use c_entity_metadata::*;
|
pub use c_entity_metadata::*;
|
||||||
pub use c_entity_status::*;
|
pub use c_entity_status::*;
|
||||||
@ -70,6 +75,7 @@ pub use c_player_chat_message::*;
|
|||||||
pub use c_player_info_update::*;
|
pub use c_player_info_update::*;
|
||||||
pub use c_player_remove::*;
|
pub use c_player_remove::*;
|
||||||
pub use c_remove_entities::*;
|
pub use c_remove_entities::*;
|
||||||
|
pub use c_reset_score::*;
|
||||||
pub use c_set_container_content::*;
|
pub use c_set_container_content::*;
|
||||||
pub use c_set_container_property::*;
|
pub use c_set_container_property::*;
|
||||||
pub use c_set_container_slot::*;
|
pub use c_set_container_slot::*;
|
||||||
@ -85,5 +91,7 @@ pub use c_unload_chunk::*;
|
|||||||
pub use c_update_entity_pos::*;
|
pub use c_update_entity_pos::*;
|
||||||
pub use c_update_entity_pos_rot::*;
|
pub use c_update_entity_pos_rot::*;
|
||||||
pub use c_update_entity_rot::*;
|
pub use c_update_entity_rot::*;
|
||||||
|
pub use c_update_objectives::*;
|
||||||
|
pub use c_update_score::*;
|
||||||
pub use c_worldevent::*;
|
pub use c_worldevent::*;
|
||||||
pub use player_action::*;
|
pub use player_action::*;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
|
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
|
use pumpkin_core::text::{style::Style, TextComponent};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -244,3 +245,13 @@ pub struct KnownPack<'a> {
|
|||||||
pub id: &'a str,
|
pub id: &'a str,
|
||||||
pub version: &'a str,
|
pub version: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
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>),
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ itertools.workspace = true
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
||||||
|
|
||||||
# Compression
|
# Compression
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
lz4 = "1.11.1"
|
lz4 = "1.11.1"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::f32::consts::PI;
|
use std::{f32::consts::PI, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::CommandSender,
|
commands::CommandSender,
|
||||||
@ -227,9 +227,13 @@ impl Player {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_chat_command(&self, server: &Server, command: SChatCommand) {
|
pub fn handle_chat_command(self: &Arc<Self>, server: &Server, command: SChatCommand) {
|
||||||
let dispatcher = server.command_dispatcher.clone();
|
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 {
|
if ADVANCED_CONFIG.commands.log_console {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Player ({}): executed command /{}",
|
"Player ({}): executed command /{}",
|
||||||
|
|||||||
@ -1,39 +1,50 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::commands::dispatcher::InvalidTreeError;
|
use crate::commands::dispatcher::InvalidTreeError;
|
||||||
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
|
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
|
||||||
use crate::commands::tree::{ConsumedArgs, RawArgs};
|
use crate::commands::tree::{ConsumedArgs, RawArgs};
|
||||||
use crate::commands::CommandSender;
|
use crate::commands::CommandSender;
|
||||||
use crate::commands::CommandSender::Player;
|
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
|
|
||||||
/// todo: implement (so far only own name + @s/@p is implemented)
|
/// todo: implement (so far only own name + @s/@p is implemented)
|
||||||
pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option<String> {
|
pub fn consume_arg_player(
|
||||||
let s = args.pop()?;
|
src: &CommandSender,
|
||||||
|
server: &Server,
|
||||||
match s {
|
args: &mut RawArgs,
|
||||||
"@s" if src.is_player() => Some(s.into()),
|
) -> Result<String, Option<String>> {
|
||||||
"@p" if src.is_player() => Some(s.into()),
|
if let Some(arg) = args.pop() {
|
||||||
"@r" => None, // todo: implement random player target selector
|
match arg {
|
||||||
"@a" | "@e" => None, // todo: implement all players target selector
|
"@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
|
// todo: implement any other player than sender
|
||||||
if let Player(player) = src {
|
for world in &server.worlds {
|
||||||
let profile = &player.gameprofile;
|
if world.get_player_by_name(name).is_some() {
|
||||||
if profile.name == s {
|
return Ok(name.into());
|
||||||
return Some(s.into());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Err(Some(format!("Player not found: {}", arg)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// todo: implement (so far only own name + @s/@p is implemented)
|
/// todo: implement (so far only own name + @s/@p is implemented)
|
||||||
pub fn parse_arg_player<'a>(
|
pub fn parse_arg_player(
|
||||||
src: &'a mut CommandSender,
|
src: &mut CommandSender,
|
||||||
_server: &Server,
|
server: &Server,
|
||||||
arg_name: &str,
|
arg_name: &str,
|
||||||
consumed_args: &ConsumedArgs,
|
consumed_args: &ConsumedArgs,
|
||||||
) -> Result<&'a crate::entity::player::Player, InvalidTreeError> {
|
) -> Result<Arc<crate::entity::player::Player>, InvalidTreeError> {
|
||||||
let s = consumed_args
|
let s = consumed_args
|
||||||
.get(arg_name)
|
.get(arg_name)
|
||||||
.ok_or(InvalidConsumptionError(None))?
|
.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()),
|
"@p" if src.is_player() => Ok(src.as_mut_player().unwrap()),
|
||||||
"@r" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement random player target selector
|
"@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
|
"@a" | "@e" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement all players target selector
|
||||||
_ => {
|
name => {
|
||||||
// todo: implement any other player than sender
|
for world in &server.worlds {
|
||||||
if let Player(player) = src {
|
if let Some(player) = world.get_player_by_name(name) {
|
||||||
let profile = &player.gameprofile;
|
|
||||||
if profile.name == s {
|
|
||||||
return Ok(player);
|
return Ok(player);
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
Err(InvalidConsumptionError(Some(s.into())))
|
Err(InvalidConsumptionError(Some(s.into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
|
|||||||
use crate::commands::tree_builder::{argument, require};
|
use crate::commands::tree_builder::{argument, require};
|
||||||
use crate::commands::CommandSender;
|
use crate::commands::CommandSender;
|
||||||
use crate::commands::CommandSender::Player;
|
use crate::commands::CommandSender::Player;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
const NAMES: [&str; 1] = ["gamemode"];
|
const NAMES: [&str; 1] = ["gamemode"];
|
||||||
|
|
||||||
@ -22,20 +23,27 @@ const DESCRIPTION: &str = "Change a player's gamemode.";
|
|||||||
const ARG_GAMEMODE: &str = "gamemode";
|
const ARG_GAMEMODE: &str = "gamemode";
|
||||||
const ARG_TARGET: &str = "target";
|
const ARG_TARGET: &str = "target";
|
||||||
|
|
||||||
pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option<String> {
|
pub fn consume_arg_gamemode(
|
||||||
let s = args.pop()?;
|
_src: &CommandSender,
|
||||||
|
_server: &Server,
|
||||||
if let Ok(id) = s.parse::<u8>() {
|
args: &mut RawArgs,
|
||||||
|
) -> Result<String, Option<String>> {
|
||||||
|
if let Some(arg) = args.pop() {
|
||||||
|
if let Ok(id) = arg.parse::<u8>() {
|
||||||
match GameMode::from_u8(id) {
|
match GameMode::from_u8(id) {
|
||||||
None | Some(GameMode::Undefined) => {}
|
None | Some(GameMode::Undefined) => {}
|
||||||
Some(_) => return Some(s.into()),
|
Some(_) => return Ok(arg.into()),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
match GameMode::from_str(s) {
|
match GameMode::from_str(arg) {
|
||||||
Err(_) | Ok(GameMode::Undefined) => None,
|
Err(_) | Ok(GameMode::Undefined) => {
|
||||||
Ok(_) => Some(s.into()),
|
return Err(Some(format!("Gamemode not found: {}", arg)))
|
||||||
}
|
}
|
||||||
|
Ok(_) => return Ok(arg.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, InvalidTreeError> {
|
pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, InvalidTreeError> {
|
||||||
@ -66,14 +74,14 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
|||||||
|
|
||||||
return if let Player(target) = sender {
|
return if let Player(target) = sender {
|
||||||
if target.gamemode.load() == gamemode {
|
if target.gamemode.load() == gamemode {
|
||||||
target.send_system_message(TextComponent::text(&format!(
|
target.send_system_message(&TextComponent::text(&format!(
|
||||||
"You already in {:?} gamemode",
|
"You already in {:?} gamemode",
|
||||||
gamemode
|
gamemode
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
// TODO
|
||||||
target.set_gamemode(gamemode);
|
target.set_gamemode(gamemode);
|
||||||
target.send_system_message(TextComponent::text(&format!(
|
target.send_system_message(&TextComponent::text(&format!(
|
||||||
"Game mode was set to {:?}",
|
"Game mode was set to {:?}",
|
||||||
gamemode
|
gamemode
|
||||||
)));
|
)));
|
||||||
@ -90,16 +98,16 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
|||||||
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
|
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
|
||||||
|
|
||||||
if target.gamemode.load() == gamemode {
|
if target.gamemode.load() == gamemode {
|
||||||
target.send_system_message(TextComponent::text(&format!(
|
sender.send_message(TextComponent::text(&format!(
|
||||||
"You already in {:?} gamemode",
|
"{} is already in {:?} gamemode",
|
||||||
gamemode
|
target.gameprofile.name, gamemode
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
// TODO
|
||||||
target.set_gamemode(gamemode);
|
target.set_gamemode(gamemode);
|
||||||
target.send_system_message(TextComponent::text(&format!(
|
sender.send_message(TextComponent::text(&format!(
|
||||||
"Game mode was set to {:?}",
|
"{}'s Game mode was set to {:?}",
|
||||||
gamemode
|
target.gameprofile.name, gamemode
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
|
|||||||
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
|
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
|
||||||
use crate::commands::tree_builder::argument;
|
use crate::commands::tree_builder::argument;
|
||||||
use crate::commands::CommandSender;
|
use crate::commands::CommandSender;
|
||||||
|
use crate::server::Server;
|
||||||
|
use pumpkin_core::text::TextComponent;
|
||||||
|
|
||||||
const NAMES: [&str; 3] = ["help", "h", "?"];
|
const NAMES: [&str; 3] = ["help", "h", "?"];
|
||||||
|
|
||||||
@ -10,12 +12,16 @@ const DESCRIPTION: &str = "Print a help message.";
|
|||||||
|
|
||||||
const ARG_COMMAND: &str = "command";
|
const ARG_COMMAND: &str = "command";
|
||||||
|
|
||||||
fn consume_arg_command(_src: &CommandSender, _args: &mut RawArgs) -> Option<String> {
|
fn consume_arg_command(
|
||||||
|
_src: &CommandSender,
|
||||||
|
_server: &Server,
|
||||||
|
_args: &mut RawArgs,
|
||||||
|
) -> Result<String, Option<String>> {
|
||||||
// let s = args.pop()?;
|
// let s = args.pop()?;
|
||||||
|
|
||||||
// dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into())
|
// dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into())
|
||||||
// TODO
|
// TODO
|
||||||
None
|
Err(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_arg_command<'a>(
|
fn parse_arg_command<'a>(
|
||||||
@ -37,12 +43,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
|||||||
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, server, args| {
|
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, server, args| {
|
||||||
let tree = parse_arg_command(args, &server.command_dispatcher)?;
|
let tree = parse_arg_command(args, &server.command_dispatcher)?;
|
||||||
|
|
||||||
// sender.send_message(TextComponent::text(&format!(
|
sender.send_message(TextComponent::text(&format!(
|
||||||
// "{} - {} Usage: {}",
|
"{} - {} Usage: {}",
|
||||||
// tree.names.join("/"),
|
tree.names.join("/"),
|
||||||
// tree.description,
|
tree.description,
|
||||||
// tree
|
tree
|
||||||
// )));
|
)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
@ -56,12 +62,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// sender.send_message(TextComponent::text(&format!(
|
sender.send_message(TextComponent::text(&format!(
|
||||||
// "{} - {} Usage: {}",
|
"{} - {} Usage: {}",
|
||||||
// tree.names.join("/"),
|
tree.names.join("/"),
|
||||||
// tree.description,
|
tree.description,
|
||||||
// tree
|
tree
|
||||||
// )));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
27
pumpkin/src/commands/cmd_kick.rs
Normal file
27
pumpkin/src/commands/cmd_kick.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::commands::arg_player::parse_arg_player;
|
||||||
|
use crate::commands::tree::CommandTree;
|
||||||
|
use crate::commands::tree_builder::argument;
|
||||||
|
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 init_command_tree<'a>() -> CommandTree<'a> {
|
||||||
|
CommandTree::new(NAMES, DESCRIPTION).with_child(
|
||||||
|
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"));
|
||||||
|
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::text("Player has been kicked.").color_named(NamedColor::Blue),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
|
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
|
||||||
use crate::commands::tree::CommandTree;
|
use crate::commands::tree::CommandTree;
|
||||||
use crate::commands::tree::RawArgs;
|
|
||||||
use crate::commands::tree_builder::argument;
|
use crate::commands::tree_builder::argument;
|
||||||
use crate::commands::CommandSender;
|
|
||||||
use pumpkin_core::text::{color::NamedColor, TextComponent};
|
use pumpkin_core::text::{color::NamedColor, TextComponent};
|
||||||
|
|
||||||
const NAMES: [&str; 1] = ["kill"];
|
const NAMES: [&str; 1] = ["kill"];
|
||||||
@ -10,13 +8,10 @@ const DESCRIPTION: &str = "Kills a target player.";
|
|||||||
|
|
||||||
const ARG_TARGET: &str = "target";
|
const ARG_TARGET: &str = "target";
|
||||||
|
|
||||||
pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option<String> {
|
|
||||||
consume_arg_player(_src, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||||
CommandTree::new(NAMES, DESCRIPTION).with_child(
|
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)?;
|
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
|
||||||
target.living_entity.kill();
|
target.living_entity.kill();
|
||||||
|
|
||||||
|
|||||||
@ -56,14 +56,17 @@ impl<'a> CommandDispatcher<'a> {
|
|||||||
println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met.");
|
println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met.");
|
||||||
return Err("Internal Error (See logs for details)".into());
|
return Err("Internal Error (See logs for details)".into());
|
||||||
}
|
}
|
||||||
Ok(is_fitting_path) => {
|
Ok(is_fitting_path) => match is_fitting_path {
|
||||||
if is_fitting_path {
|
Ok(_) => return Ok(()),
|
||||||
return Ok(());
|
Err(error) => {
|
||||||
|
// Custom error message or not ?
|
||||||
|
if let Some(error) = error {
|
||||||
|
return Err(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format!("Invalid Syntax. Usage: {}", tree))
|
Err(format!("Invalid Syntax. Usage: {}", tree))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ impl<'a> CommandDispatcher<'a> {
|
|||||||
path: Vec<usize>,
|
path: Vec<usize>,
|
||||||
tree: &CommandTree,
|
tree: &CommandTree,
|
||||||
mut raw_args: RawArgs,
|
mut raw_args: RawArgs,
|
||||||
) -> Result<bool, InvalidTreeError> {
|
) -> Result<Result<(), Option<String>>, InvalidTreeError> {
|
||||||
let mut parsed_args: ConsumedArgs = HashMap::new();
|
let mut parsed_args: ConsumedArgs = HashMap::new();
|
||||||
|
|
||||||
for node in path.iter().map(|&i| &tree.nodes[i]) {
|
for node in path.iter().map(|&i| &tree.nodes[i]) {
|
||||||
@ -96,36 +99,37 @@ impl<'a> CommandDispatcher<'a> {
|
|||||||
NodeType::ExecuteLeaf { run } => {
|
NodeType::ExecuteLeaf { run } => {
|
||||||
return if raw_args.is_empty() {
|
return if raw_args.is_empty() {
|
||||||
run(src, server, &parsed_args)?;
|
run(src, server, &parsed_args)?;
|
||||||
Ok(true)
|
Ok(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
Ok(Err(None))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
NodeType::Literal { string, .. } => {
|
NodeType::Literal { string, .. } => {
|
||||||
if raw_args.pop() != Some(string) {
|
if raw_args.pop() != Some(string) {
|
||||||
return Ok(false);
|
return Ok(Err(None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeType::Argument {
|
NodeType::Argument {
|
||||||
consumer: consume,
|
consumer: consume,
|
||||||
name,
|
name,
|
||||||
..
|
..
|
||||||
} => {
|
} => match consume(src, server, &mut raw_args) {
|
||||||
if let Some(consumed) = consume(src, &mut raw_args) {
|
Ok(consumed) => {
|
||||||
parsed_args.insert(name, consumed);
|
parsed_args.insert(name, consumed);
|
||||||
} else {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Ok(Err(err));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
NodeType::Require { predicate, .. } => {
|
NodeType::Require { predicate, .. } => {
|
||||||
if !predicate(src) {
|
if !predicate(src) {
|
||||||
return Ok(false);
|
return Ok(Err(None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(Err(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a command with the dispatcher.
|
/// Register a command with the dispatcher.
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dispatcher::InvalidTreeError;
|
use dispatcher::InvalidTreeError;
|
||||||
use pumpkin_core::text::TextComponent;
|
use pumpkin_core::text::TextComponent;
|
||||||
use tree::ConsumedArgs;
|
use tree::ConsumedArgs;
|
||||||
@ -9,6 +11,7 @@ mod arg_player;
|
|||||||
mod cmd_echest;
|
mod cmd_echest;
|
||||||
mod cmd_gamemode;
|
mod cmd_gamemode;
|
||||||
mod cmd_help;
|
mod cmd_help;
|
||||||
|
mod cmd_kick;
|
||||||
mod cmd_kill;
|
mod cmd_kill;
|
||||||
mod cmd_pumpkin;
|
mod cmd_pumpkin;
|
||||||
mod cmd_stop;
|
mod cmd_stop;
|
||||||
@ -20,15 +23,15 @@ mod tree_format;
|
|||||||
pub enum CommandSender<'a> {
|
pub enum CommandSender<'a> {
|
||||||
Rcon(&'a mut Vec<String>),
|
Rcon(&'a mut Vec<String>),
|
||||||
Console,
|
Console,
|
||||||
Player(&'a Player),
|
Player(Arc<Player>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CommandSender<'a> {
|
impl<'a> CommandSender<'a> {
|
||||||
pub async fn send_message(&mut self, text: TextComponent<'a>) {
|
pub fn send_message(&mut self, text: TextComponent) {
|
||||||
match self {
|
match self {
|
||||||
// TODO: add color and stuff to console
|
// TODO: add color and stuff to console
|
||||||
CommandSender::Console => log::info!("{}", text.to_pretty_console()),
|
CommandSender::Console => log::info!("{}", text.to_pretty_console()),
|
||||||
CommandSender::Player(c) => c.send_system_message(text).await,
|
CommandSender::Player(c) => c.send_system_message(&text),
|
||||||
CommandSender::Rcon(s) => s.push(text.to_pretty_console()),
|
CommandSender::Rcon(s) => s.push(text.to_pretty_console()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,9 +51,9 @@ impl<'a> CommandSender<'a> {
|
|||||||
CommandSender::Rcon(_) => true,
|
CommandSender::Rcon(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_mut_player(&mut self) -> Option<&Player> {
|
pub fn as_mut_player(&mut self) -> Option<Arc<Player>> {
|
||||||
match self {
|
match self {
|
||||||
CommandSender::Player(player) => Some(player),
|
CommandSender::Player(player) => Some(player.clone()),
|
||||||
CommandSender::Console => None,
|
CommandSender::Console => None,
|
||||||
CommandSender::Rcon(_) => None,
|
CommandSender::Rcon(_) => None,
|
||||||
}
|
}
|
||||||
@ -75,6 +78,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> {
|
|||||||
dispatcher.register(cmd_help::init_command_tree());
|
dispatcher.register(cmd_help::init_command_tree());
|
||||||
dispatcher.register(cmd_echest::init_command_tree());
|
dispatcher.register(cmd_echest::init_command_tree());
|
||||||
dispatcher.register(cmd_kill::init_command_tree());
|
dispatcher.register(cmd_kill::init_command_tree());
|
||||||
|
dispatcher.register(cmd_kick::init_command_tree());
|
||||||
|
|
||||||
dispatcher
|
dispatcher
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use super::RunFunctionType;
|
use super::RunFunctionType;
|
||||||
use crate::commands::CommandSender;
|
use crate::{commands::CommandSender, server::Server};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
/// see [crate::commands::tree_builder::argument]
|
/// 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>;
|
pub type ConsumedArgs<'a> = HashMap<&'a str, String>;
|
||||||
|
|
||||||
/// see [crate::commands::tree_builder::argument]
|
/// see [crate::commands::tree_builder::argument]
|
||||||
pub type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option<String>;
|
/// 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<String, Option<String>>;
|
||||||
|
|
||||||
pub struct Node<'a> {
|
pub struct Node<'a> {
|
||||||
pub(crate) children: Vec<usize>,
|
pub(crate) children: Vec<usize>,
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
use std::{fs::File, path::Path};
|
use core::error;
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{Cursor, Read},
|
||||||
|
path::Path,
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
|
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
|
||||||
@ -9,6 +15,29 @@ use pumpkin_protocol::{
|
|||||||
|
|
||||||
use super::CURRENT_MC_VERSION;
|
use super::CURRENT_MC_VERSION;
|
||||||
|
|
||||||
|
static DEFAULT_ICON: LazyLock<&[u8]> =
|
||||||
|
LazyLock::new(|| include_bytes!("../../../assets/default_icon.png"));
|
||||||
|
|
||||||
|
fn load_icon_from_file<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn error::Error>> {
|
||||||
|
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<String, Box<dyn error::Error>> {
|
||||||
|
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 {
|
pub struct CachedStatus {
|
||||||
_status_response: StatusResponse,
|
_status_response: StatusResponse,
|
||||||
// We cache the json response here so we don't parse it every time someone makes a Status request.
|
// We cache the json response here so we don't parse it every time someone makes a Status request.
|
||||||
@ -57,10 +86,21 @@ impl CachedStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_response(config: &BasicConfiguration) -> StatusResponse {
|
pub fn build_response(config: &BasicConfiguration) -> StatusResponse {
|
||||||
let icon_path = "/icon.png";
|
let icon = if config.use_favicon {
|
||||||
let icon = if Path::new(icon_path).exists() {
|
let icon_path = &config.favicon_path;
|
||||||
Some(Self::load_icon(icon_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(err) => {
|
||||||
|
log::warn!("Failed to load default icon: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
log::info!("Not using a server favicon");
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,21 +122,4 @@ impl CachedStatus {
|
|||||||
enforce_secure_chat: false,
|
enforce_secure_chat: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_icon<P: AsRef<Path>>(path: P) -> String {
|
|
||||||
let icon = png::Decoder::new(File::open(path).expect("Failed to load icon"));
|
|
||||||
let mut 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");
|
|
||||||
// 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()];
|
|
||||||
let mut result = "data:image/png;base64,".to_owned();
|
|
||||||
general_purpose::STANDARD.encode_string(bytes, &mut result);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,12 @@ use pumpkin_protocol::{
|
|||||||
ClientPacket, VarInt,
|
ClientPacket, VarInt,
|
||||||
};
|
};
|
||||||
use pumpkin_world::level::Level;
|
use pumpkin_world::level::Level;
|
||||||
|
use scoreboard::Scoreboard;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub mod scoreboard;
|
||||||
|
|
||||||
/// Represents a Minecraft world, containing entities, players, and the underlying level data.
|
/// Represents a Minecraft world, containing entities, players, and the underlying level data.
|
||||||
///
|
///
|
||||||
/// Each dimension (Overworld, Nether, End) typically has its own `World`.
|
/// Each dimension (Overworld, Nether, End) typically has its own `World`.
|
||||||
@ -35,6 +38,7 @@ pub struct World {
|
|||||||
pub level: Arc<Mutex<Level>>,
|
pub level: Arc<Mutex<Level>>,
|
||||||
/// A map of active players within the world, keyed by their unique token.
|
/// A map of active players within the world, keyed by their unique token.
|
||||||
pub current_players: Arc<Mutex<HashMap<usize, Arc<Player>>>>,
|
pub current_players: Arc<Mutex<HashMap<usize, Arc<Player>>>>,
|
||||||
|
pub scoreboard: Mutex<Scoreboard>,
|
||||||
// TODO: entities
|
// TODO: entities
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +47,7 @@ impl World {
|
|||||||
Self {
|
Self {
|
||||||
level: Arc::new(Mutex::new(level)),
|
level: Arc::new(Mutex::new(level)),
|
||||||
current_players: Arc::new(Mutex::new(HashMap::new())),
|
current_players: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
scoreboard: Mutex::new(Scoreboard::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
123
pumpkin/src/world/scoreboard.rs
Normal file
123
pumpkin/src/world/scoreboard.rs
Normal file
@ -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<String, ScoreboardObjective<'static>>,
|
||||||
|
// teams: HashMap<String, Team>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<NumberFormat<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ScoreboardObjective<'a> {
|
||||||
|
pub const fn new(
|
||||||
|
name: &'a str,
|
||||||
|
display_name: TextComponent<'a>,
|
||||||
|
render_type: RenderType,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
) -> 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<TextComponent<'a>>,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ScoreboardScore<'a> {
|
||||||
|
pub const fn new(
|
||||||
|
entity_name: &'a str,
|
||||||
|
objective_name: &'a str,
|
||||||
|
value: VarInt,
|
||||||
|
display_name: Option<TextComponent<'a>>,
|
||||||
|
number_format: Option<NumberFormat<'a>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_name,
|
||||||
|
objective_name,
|
||||||
|
value,
|
||||||
|
display_name,
|
||||||
|
number_format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user