Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added native operator permission management #348

Merged
merged 15 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ services:
pumpkin:
build: .
ports:
- 25565:25565
- "25565:25565"
volumes:
- ./data:/pumpkin
stdin_open: true
tty: true
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions pumpkin-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ edition.workspace = true
pumpkin-core = { path = "../pumpkin-core" }
serde.workspace = true
log.workspace = true
uuid.workspace = true

toml = "0.8"
5 changes: 5 additions & 0 deletions pumpkin-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use log::warn;
use logging::LoggingConfig;
use op::OpLevel;
use pumpkin_core::{Difficulty, GameMode};
use query::QueryConfig;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
Expand Down Expand Up @@ -28,6 +29,7 @@ pub use server_links::ServerLinksConfig;
mod commands;
pub mod compression;
mod lan_broadcast;
pub mod op;
mod pvp;
mod rcon;
mod server_links;
Expand Down Expand Up @@ -76,6 +78,8 @@ pub struct BasicConfiguration {
pub simulation_distance: u8,
/// The default game difficulty.
pub default_difficulty: Difficulty,
/// The op level assign by the /op command
pub op_permission_level: OpLevel,
/// Whether the Nether dimension is enabled.
pub allow_nether: bool,
/// Whether the server is in hardcore mode.
Expand Down Expand Up @@ -106,6 +110,7 @@ impl Default for BasicConfiguration {
view_distance: 10,
simulation_distance: 10,
default_difficulty: Difficulty::Normal,
op_permission_level: OpLevel::Owner,
allow_nether: true,
hardcore: false,
online_mode: true,
Expand Down
49 changes: 49 additions & 0 deletions pumpkin-config/src/op.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;

#[derive(Clone, Copy)]
#[repr(u8)]
pub enum OpLevel {
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
None = 0,
Basic = 1,
Moderator = 2,
Admin = 3,
Owner = 4,
}

impl Serialize for OpLevel {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_u8(*self as u8)
}
}

impl<'de> Deserialize<'de> for OpLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(OpLevel::None),
1 => Ok(OpLevel::Basic),
2 => Ok(OpLevel::Moderator),
3 => Ok(OpLevel::Admin),
4 => Ok(OpLevel::Owner),
_ => Err(serde::de::Error::custom(format!(
"Invalid value for OpLevel: {}",
value
))),
}
}
}

KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Serialize, Deserialize, Clone)]
pub struct Op {
pub uuid: Uuid,
pub name: String,
pub level: OpLevel,
pub bypasses_player_limit: bool,
}
77 changes: 77 additions & 0 deletions pumpkin/src/command/commands/cmd_op.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::{
command::{
args::{arg_players::PlayersArgumentConsumer, Arg, ConsumedArgs},
tree::CommandTree,
tree_builder::{argument, require},
CommandError, CommandExecutor, CommandSender,
},
entity::player::PermissionLvl,
server::json_config::{SaveJSONConfiguration, OPERATOR_CONFIG},
};
use async_trait::async_trait;
use pumpkin_config::{op::Op, BASIC_CONFIG};
use pumpkin_core::text::TextComponent;
use CommandError::InvalidConsumption;

const NAMES: [&str; 1] = ["op"];
const DESCRIPTION: &str = "Specifies one or more game profiles (player profiles). Must be a player name (should be a real one if the server is in online mode) or a player-type target selector";
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
const ARG_TARGET: &str = "player";

struct OpExecutor;

#[async_trait]
impl CommandExecutor for OpExecutor {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let mut config = OPERATOR_CONFIG.write().await;

let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else {
return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};

// log each player to the console.
for player in targets {
let op_entry = Op {
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
uuid: player.gameprofile.id,
name: player.gameprofile.name.clone(),
level: BASIC_CONFIG.op_permission_level,
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
bypasses_player_limit: false,
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
};
if let Some(op) = config
.ops
.iter_mut()
.find(|o| o.uuid == player.gameprofile.id)
{
op.level = BASIC_CONFIG.op_permission_level;
} else {
config.ops.push(op_entry);
}
config.save();

player
.set_permission_lvl(
BASIC_CONFIG.op_permission_level.into(),
&server.command_dispatcher,
)
.await;

let player_name = player.gameprofile.name.clone();
let message = format!("Made {player_name} a server operator.");
let msg = TextComponent::text(&message);
sender.send_message(msg).await;
}

Ok(())
}
}

pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.has_permission_lvl(PermissionLvl::Four))
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
.with_child(argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&OpExecutor)),
)
}
1 change: 1 addition & 0 deletions pumpkin/src/command/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod cmd_help;
pub mod cmd_kick;
pub mod cmd_kill;
pub mod cmd_list;
pub mod cmd_op;
pub mod cmd_pumpkin;
pub mod cmd_say;
pub mod cmd_seed;
Expand Down
2 changes: 2 additions & 0 deletions pumpkin/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::server::Server;
use crate::world::World;
use args::ConsumedArgs;
use async_trait::async_trait;
use commands::cmd_op;
use commands::{
cmd_clear, cmd_craft, cmd_echest, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick,
cmd_kill, cmd_list, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time,
Expand Down Expand Up @@ -131,6 +132,7 @@ pub fn default_dispatcher<'a>() -> Arc<CommandDispatcher<'a>> {
dispatcher.register(cmd_seed::init_command_tree());
dispatcher.register(cmd_transfer::init_command_tree());
dispatcher.register(cmd_fill::init_command_tree());
dispatcher.register(cmd_op::init_command_tree());

Arc::new(dispatcher)
}
Expand Down
51 changes: 42 additions & 9 deletions pumpkin/src/entity/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{
use crossbeam::atomic::AtomicCell;
use itertools::Itertools;
use num_derive::{FromPrimitive, ToPrimitive};
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_config::{op::OpLevel, ADVANCED_CONFIG};
use pumpkin_core::{
math::{
boundingbox::{BoundingBox, BoundingBoxSize},
Expand Down Expand Up @@ -46,16 +46,17 @@ use tokio::sync::{Mutex, Notify};
use tokio::task::JoinHandle;

use super::Entity;
use crate::error::PumpkinError;
use crate::{
client::{
authentication::GameProfile,
combat::{self, player_attack_sound, AttackType},
Client, PlayerConfig,
},
command::{client_cmd_suggestions, dispatcher::CommandDispatcher},
server::Server,
world::World,
};
use crate::{error::PumpkinError, server::json_config::OPERATOR_CONFIG};

use super::living::LivingEntity;

Expand Down Expand Up @@ -158,7 +159,7 @@ pub struct Player {
cancel_tasks: Notify,

/// the players op permission level
permission_lvl: PermissionLvl,
permission_lvl: parking_lot::Mutex<PermissionLvl>,
}

impl Player {
Expand All @@ -180,6 +181,8 @@ impl Player {
},
|profile| profile,
);

let gameprofile_clone = gameprofile.clone();
let config = client.config.lock().await.clone().unwrap_or_default();
let view_distance = config.view_distance;
let bounding_box_size = BoundingBoxSize {
Expand Down Expand Up @@ -218,8 +221,17 @@ impl Player {
pending_chunks: Arc::new(parking_lot::Mutex::new(HashMap::new())),
pending_chunk_batch: parking_lot::Mutex::new(HashMap::new()),
cancel_tasks: Notify::new(),
// TODO: change this
permission_lvl: PermissionLvl::Four,
// Minecraft has no why to change the default permission level of new players.
// Minecrafts default permission level is 0
permission_lvl: OPERATOR_CONFIG
KairuDeibisu marked this conversation as resolved.
Show resolved Hide resolved
.read()
.await
.ops
.iter()
.find(|op| op.uuid == gameprofile_clone.id)
.map_or(parking_lot::Mutex::new(PermissionLvl::Zero), |op| {
parking_lot::Mutex::new(op.level.into())
}),
}
}

Expand Down Expand Up @@ -506,20 +518,29 @@ impl Player {
self.client
.send_packet(&CEntityStatus::new(
self.entity_id(),
24 + self.permission_lvl as i8,
24 + self.permission_lvl() as i8,
))
.await;
}

/// sets the players permission level and syncs it with the client
pub async fn set_permission_lvl(&mut self, lvl: PermissionLvl) {
self.permission_lvl = lvl;
pub async fn set_permission_lvl(
self: &Arc<Self>,
lvl: PermissionLvl,
command_dispatcher: &Arc<CommandDispatcher<'static>>,
) {
{
let mut level = self.permission_lvl.lock();
*level = lvl;
}

self.send_permission_lvl_update().await;
client_cmd_suggestions::send_c_commands_packet(self, command_dispatcher).await;
}

/// get the players permission level
pub fn permission_lvl(&self) -> PermissionLvl {
self.permission_lvl
*self.permission_lvl.lock()
}

/// yaw and pitch in degrees
Expand Down Expand Up @@ -863,3 +884,15 @@ pub enum PermissionLvl {
Three = 3,
Four = 4,
}

impl From<OpLevel> for PermissionLvl {
fn from(op: OpLevel) -> Self {
match op {
OpLevel::None => PermissionLvl::Zero,
OpLevel::Basic => PermissionLvl::One,
OpLevel::Moderator => PermissionLvl::Two,
OpLevel::Admin => PermissionLvl::Three,
OpLevel::Owner => PermissionLvl::Four,
}
}
}
Loading
Loading