Skip to content

Commit

Permalink
Added native operator permission management (#348)
Browse files Browse the repository at this point in the history
* Starting work on Operator permission system

This commit adds the ops.json configuration found in vanilla
servers, and updates the basic configuration to handle the default
permission level.

Server now uses ops file and defaults players to op level 0

The /op command has been added but needs players to rejoin for now.

* Clippy Fix + need to Rejoin after /op removed

I found the source of the DeadLock. Updated set permission function.

Fix cargo formatting issues and clippy issues.

* Remove temp warn message

* Move OperatorConfig to server

As Snowiii pointed out, OperatorConfig is runtime data.

Revert most changes in pumpkin-config.

op_permission_level must say in the basic configuration for
parity with Minecraft.

* cargo fmt + cargo clippy

* Sync permission change with client

* Fixs issues @Commandcracker found in review

- Move PermissionLvl to core and removed OpLevel
- Move ops.json to /data/ops.json
- Fix permissions issue with /op command
- Shorten /op command description

* refactor into `data` folder

* create data dir when needed

* add to readme

* fix: conflicts

---------

Co-authored-by: Alexander Medvedev <[email protected]>
  • Loading branch information
KairuDeibisu and Snowiiii authored Dec 27, 2024
1 parent c521ef8 commit 6a5add8
Show file tree
Hide file tree
Showing 25 changed files with 346 additions and 60 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [x] Particles
- [x] Chat
- [x] Commands
- [x] OP Permission
- Proxy
- [x] Bungeecord
- [x] Velocity
Expand Down
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
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"
6 changes: 5 additions & 1 deletion pumpkin-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use log::warn;
use logging::LoggingConfig;
use pumpkin_core::{Difficulty, GameMode};
use pumpkin_core::{Difficulty, GameMode, PermissionLvl};
use query::QueryConfig;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

Expand Down Expand Up @@ -29,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 @@ -79,6 +80,8 @@ pub struct BasicConfiguration {
pub simulation_distance: NonZeroU8,
/// The default game difficulty.
pub default_difficulty: Difficulty,
/// The op level assign by the /op command
pub op_permission_level: PermissionLvl,
/// Whether the Nether dimension is enabled.
pub allow_nether: bool,
/// Whether the server is in hardcore mode.
Expand Down Expand Up @@ -109,6 +112,7 @@ impl Default for BasicConfiguration {
view_distance: NonZeroU8::new(10).unwrap(),
simulation_distance: NonZeroU8::new(10).unwrap(),
default_difficulty: Difficulty::Normal,
op_permission_level: PermissionLvl::Four,
allow_nether: true,
hardcore: false,
online_mode: true,
Expand Down
27 changes: 27 additions & 0 deletions pumpkin-config/src/op.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use pumpkin_core::permission::PermissionLvl;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Serialize, Deserialize, Clone, Default)]
pub struct Op {
pub uuid: Uuid,
pub name: String,
pub level: PermissionLvl,
pub bypasses_player_limit: bool,
}

impl Op {
pub fn new(
uuid: Uuid,
name: String,
level: PermissionLvl,
bypasses_player_limit: bool,
) -> Self {
Self {
uuid,
name,
level,
bypasses_player_limit,
}
}
}
2 changes: 2 additions & 0 deletions pumpkin-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod gamemode;
pub mod math;
pub mod permission;
pub mod random;
pub mod text;

pub use gamemode::GameMode;
pub use permission::PermissionLvl;

use serde::{Deserialize, Serialize};

Expand Down
56 changes: 56 additions & 0 deletions pumpkin-core/src/permission.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use num_derive::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Represents the player's permission level
///
/// Permission levels determine the player's access to commands and server operations.
/// Each numeric level corresponds to a specific role:
/// - `Zero`: `normal`: Player can use basic commands.
/// - `One`: `moderator`: Player can bypass spawn protection.
/// - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks.
/// - `Three`: `admin`: Player or executor can use commands related to multiplayer management.
/// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management.
#[derive(FromPrimitive, ToPrimitive, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i8)]
pub enum PermissionLvl {
#[default]
Zero = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
}

impl PartialOrd for PermissionLvl {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
(*self as u8).partial_cmp(&(*other as u8))
}
}

impl Serialize for PermissionLvl {
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 PermissionLvl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(PermissionLvl::Zero),
2 => Ok(PermissionLvl::Two),
3 => Ok(PermissionLvl::Three),
4 => Ok(PermissionLvl::Four),
_ => Err(serde::de::Error::custom(format!(
"Invalid value for OpLevel: {}",
value
))),
}
}
}
3 changes: 2 additions & 1 deletion pumpkin/src/command/commands/cmd_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use crate::command::args::{ConsumedArgs, FindArg};
use crate::command::tree::CommandTree;
use crate::command::tree_builder::{argument, literal, require};
use crate::command::{CommandError, CommandExecutor, CommandSender};
use crate::entity::player::PermissionLvl;

use async_trait::async_trait;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_core::math::vector3::Vector3;
use pumpkin_core::permission::PermissionLvl;
use pumpkin_core::text::TextComponent;

const NAMES: [&str; 1] = ["fill"];
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_gamemode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use async_trait::async_trait;
use crate::command::args::arg_gamemode::GamemodeArgumentConsumer;
use crate::command::args::GetCloned;

use crate::entity::player::PermissionLvl;
use crate::TextComponent;
use pumpkin_core::permission::PermissionLvl;

use crate::command::args::arg_players::PlayersArgumentConsumer;

Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_give.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName};
use crate::command::tree::CommandTree;
use crate::command::tree_builder::{argument, argument_default_name, require};
use crate::command::{CommandError, CommandExecutor, CommandSender};
use crate::entity::player::PermissionLvl;
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 1] = ["give"];

Expand Down
1 change: 1 addition & 0 deletions pumpkin/src/command/commands/cmd_kick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl CommandExecutor for KickExecutor {
}
}

// TODO: Permission
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION)
.with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(KickExecutor))
Expand Down
80 changes: 80 additions & 0 deletions pumpkin/src/command/commands/cmd_op.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::{
command::{
args::{arg_players::PlayersArgumentConsumer, Arg, ConsumedArgs},
tree::CommandTree,
tree_builder::{argument, require},
CommandError, CommandExecutor, CommandSender,
},
data::{op_data::OPERATOR_CONFIG, SaveJSONConfiguration},
};
use async_trait::async_trait;
use pumpkin_config::{op::Op, BASIC_CONFIG};
use pumpkin_core::permission::PermissionLvl;
use pumpkin_core::text::TextComponent;
use CommandError::InvalidConsumption;

const NAMES: [&str; 1] = ["op"];
const DESCRIPTION: &str = "Grants operator status to a player.";
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 new_level = if BASIC_CONFIG.op_permission_level > sender.permission_lvl() {
sender.permission_lvl()
} else {
BASIC_CONFIG.op_permission_level
};

let op_entry = Op::new(
player.gameprofile.id,
player.gameprofile.name.clone(),
new_level,
false,
);
if let Some(op) = config
.ops
.iter_mut()
.find(|o| o.uuid == player.gameprofile.id)
{
op.level = new_level;
} else {
config.ops.push(op_entry);
}
config.save();

player
.set_permission_lvl(new_level, &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() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(|sender| sender.has_permission_lvl(PermissionLvl::Three))
.with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(OpExecutor)),
)
}
14 changes: 6 additions & 8 deletions pumpkin/src/command/commands/cmd_say.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ use async_trait::async_trait;
use pumpkin_core::text::TextComponent;
use pumpkin_protocol::client::play::CSystemChatMessage;

use crate::{
command::{
args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs},
tree::CommandTree,
tree_builder::{argument, require},
CommandError, CommandExecutor, CommandSender,
},
entity::player::PermissionLvl,
use crate::command::{
args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs},
tree::CommandTree,
tree_builder::{argument, require},
CommandError, CommandExecutor, CommandSender,
};
use pumpkin_core::permission::PermissionLvl;
use CommandError::InvalidConsumption;

const NAMES: [&str; 1] = ["say"];
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::command::tree_builder::require;
use crate::command::{
args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender,
};
use crate::entity::player::PermissionLvl;
use async_trait::async_trait;
use pumpkin_core::permission::PermissionLvl;
use pumpkin_core::text::click::ClickEvent;
use pumpkin_core::text::hover::HoverEvent;
use pumpkin_core::text::{color::NamedColor, TextComponent};
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_setblock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::command::args::{ConsumedArgs, FindArg};
use crate::command::tree::CommandTree;
use crate::command::tree_builder::{argument, literal, require};
use crate::command::{CommandError, CommandExecutor, CommandSender};
use crate::entity::player::PermissionLvl;
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 1] = ["setblock"];

Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_stop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::command::args::ConsumedArgs;
use crate::command::tree::CommandTree;
use crate::command::tree_builder::require;
use crate::command::{CommandError, CommandExecutor, CommandSender};
use crate::entity::player::PermissionLvl;
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 1] = ["stop"];

Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_teleport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::command::tree::CommandTree;
use crate::command::tree_builder::{argument, literal, require};
use crate::command::CommandError;
use crate::command::{CommandExecutor, CommandSender};
use crate::entity::player::PermissionLvl;
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 2] = ["teleport", "tp"];
const DESCRIPTION: &str = "Teleports entities, including players."; // todo
Expand Down
10 changes: 4 additions & 6 deletions pumpkin/src/command/commands/cmd_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ use pumpkin_core::text::TextComponent;
use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::FindArgDefaultName;
use crate::command::tree_builder::{argument_default_name, literal};
use crate::{
command::{
tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender,
ConsumedArgs,
},
entity::player::PermissionLvl,
use crate::command::{
tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender,
ConsumedArgs,
};
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 1] = ["time"];

Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::command::tree_builder::{argument, argument_default_name, require};
use crate::command::{
args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender,
};
use crate::entity::player::PermissionLvl;
use pumpkin_core::permission::PermissionLvl;

const NAMES: [&str; 1] = ["transfer"];

Expand Down
1 change: 1 addition & 0 deletions pumpkin/src/command/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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
Loading

0 comments on commit 6a5add8

Please sign in to comment.