diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 65255e993..486a1f9c0 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -11,7 +11,6 @@ use std::{ path::Path, sync::LazyLock, }; - pub mod logging; pub mod networking; @@ -90,6 +89,8 @@ pub struct BasicConfiguration { pub tps: f32, /// The default game mode for players. pub default_gamemode: GameMode, + /// If the server force the gamemode on join + pub force_gamemode: bool, /// Whether to remove IPs from logs or not pub scrub_ips: bool, /// Whether to use a server favicon @@ -115,6 +116,7 @@ impl Default for BasicConfiguration { motd: "A Blazing fast Pumpkin Server!".to_string(), tps: 20.0, default_gamemode: GameMode::Survival, + force_gamemode: false, scrub_ips: true, use_favicon: true, favicon_path: "icon.png".to_string(), diff --git a/pumpkin/src/command/commands/defaultgamemode.rs b/pumpkin/src/command/commands/defaultgamemode.rs new file mode 100644 index 000000000..ce587cd3f --- /dev/null +++ b/pumpkin/src/command/commands/defaultgamemode.rs @@ -0,0 +1,64 @@ +use crate::command::args::gamemode::GamemodeArgumentConsumer; +use crate::command::args::{Arg, GetCloned}; +use crate::command::dispatcher::CommandError::InvalidConsumption; +use crate::command::tree::builder::argument; +use crate::command::{ + CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree, +}; +use async_trait::async_trait; +use pumpkin_config::BASIC_CONFIG; +use pumpkin_util::GameMode; +use pumpkin_util::text::TextComponent; + +const NAMES: [&str; 1] = ["defaultgamemode"]; + +const DESCRIPTION: &str = "Change the default gamemode"; + +pub const ARG_GAMEMODE: &str = "gamemode"; + +pub struct DefaultGamemode { + pub gamemode: GameMode, +} + +struct DefaultGamemodeExecutor; + +#[async_trait] +impl CommandExecutor for DefaultGamemodeExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { + return Err(InvalidConsumption(Some(ARG_GAMEMODE.into()))); + }; + + if BASIC_CONFIG.force_gamemode { + for player in server.get_all_players().await { + player.set_gamemode(gamemode).await; + } + } + + let gamemode_string = format!("{gamemode:?}").to_lowercase(); + let gamemode_string = format!("gameMode.{gamemode_string}"); + + sender + .send_message(TextComponent::translate( + "commands.defaultgamemode.success", + [TextComponent::translate(gamemode_string, [])], + )) + .await; + + //Change the default gamemode (not in configuration.toml) + server.defaultgamemode.lock().await.gamemode = gamemode; + + Ok(()) + } +} + +#[must_use] +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION) + .then(argument(ARG_GAMEMODE, GamemodeArgumentConsumer).execute(DefaultGamemodeExecutor)) +} diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 87afd04ff..bee0e6959 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -8,6 +8,7 @@ mod banlist; mod bossbar; mod clear; mod damage; +pub mod defaultgamemode; mod deop; mod effect; mod experience; @@ -72,6 +73,7 @@ pub fn default_dispatcher() -> CommandDispatcher { dispatcher.register(bossbar::init_command_tree(), PermissionLvl::Two); dispatcher.register(say::init_command_tree(), PermissionLvl::Two); dispatcher.register(gamemode::init_command_tree(), PermissionLvl::Two); + dispatcher.register(defaultgamemode::init_command_tree(), PermissionLvl::Two); // Three dispatcher.register(op::init_command_tree(), PermissionLvl::Three); dispatcher.register(deop::init_command_tree(), PermissionLvl::Three); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 88404860f..d5e674fc4 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,3 +1,15 @@ +use crate::block::default_block_properties_manager; +use crate::block::properties::BlockPropertiesManager; +use crate::block::registry::BlockRegistry; +use crate::command::commands::default_dispatcher; +use crate::command::commands::defaultgamemode::DefaultGamemode; +use crate::entity::{Entity, EntityId}; +use crate::item::registry::ItemRegistry; +use crate::net::EncryptionError; +use crate::world::custom_bossbar::CustomBossbars; +use crate::{ + command::dispatcher::CommandDispatcher, entity::player::Player, net::Client, world::World, +}; use connection_cache::{CachedBranding, CachedStatus}; use crossbeam::atomic::AtomicCell; use key_store::KeyStore; @@ -28,18 +40,6 @@ use std::{ }; use tokio::sync::{Mutex, RwLock}; -use crate::block::default_block_properties_manager; -use crate::block::properties::BlockPropertiesManager; -use crate::block::registry::BlockRegistry; -use crate::command::commands::default_dispatcher; -use crate::entity::{Entity, EntityId}; -use crate::item::registry::ItemRegistry; -use crate::net::EncryptionError; -use crate::world::custom_bossbar::CustomBossbars; -use crate::{ - command::dispatcher::CommandDispatcher, entity::player::Player, net::Client, world::World, -}; - mod connection_cache; mod key_store; pub mod ticker; @@ -80,6 +80,8 @@ pub struct Server { pub auth_client: Option, /// The server's custom bossbars pub bossbars: Mutex, + /// The default gamemode when a player joins the server (reset every restart) + pub defaultgamemode: Mutex, } impl Server { @@ -137,6 +139,9 @@ impl Server { server_listing: Mutex::new(CachedStatus::new()), server_branding: CachedBranding::new(), bossbars: Mutex::new(CustomBossbars::new()), + defaultgamemode: Mutex::new(DefaultGamemode { + gamemode: BASIC_CONFIG.default_gamemode, + }), } } @@ -175,7 +180,7 @@ impl Server { /// You still have to spawn the Player in the World to make then to let them Join and make them Visible pub async fn add_player(&self, client: Arc) -> (Arc, Arc) { let entity_id = self.new_entity_id(); - let gamemode = BASIC_CONFIG.default_gamemode; + let gamemode = self.defaultgamemode.lock().await.gamemode; // Basically the default world // TODO: select default from config let world = &self.worlds.read().await[0];