steel_core/command/builtins/
difficulty.rs1use steel_utils::{Identifier, translations, types::Difficulty};
2use text_components::{TextComponent, translation::Translation};
3
4use super::super::{
5 brigadier::{CommandNodeBuilder, CommandSyntaxError},
6 execution::{CommandSource, SteelCommandContext, SteelCommandRuntime, literal},
7 registration::CommandRegistration,
8};
9use crate::permission::{PermissionContext, PermissionExpr, PermissionKey, PermissionKeyError};
10use crate::player::Player;
11use crate::world::World;
12
13pub(super) fn registration() -> CommandRegistration<CommandSource> {
14 CommandRegistration::new(Identifier::vanilla_static("difficulty"), |_| command())
15}
16
17pub(crate) fn player_can_change_difficulty(player: &Player, world: &World) -> bool {
18 let permission = match difficulty_permission() {
19 Ok(permission) => permission,
20 Err(error) => {
21 log::error!("invalid built-in difficulty permission key: {error}");
22 return false;
23 }
24 };
25 let context = PermissionContext::for_world(world.key.clone());
26 player.has_permission_in(&permission, &context)
27}
28
29fn difficulty_permission() -> Result<PermissionExpr, PermissionKeyError> {
30 PermissionKey::parse("minecraft.command.difficulty").map(PermissionExpr::key)
31}
32
33fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
34 literal("difficulty")
35 .executes(query_difficulty)
36 .then(difficulty_literal("peaceful", Difficulty::Peaceful))
37 .then(difficulty_literal("easy", Difficulty::Easy))
38 .then(difficulty_literal("normal", Difficulty::Normal))
39 .then(difficulty_literal("hard", Difficulty::Hard))
40}
41
42fn difficulty_literal(
43 name: &'static str,
44 difficulty: Difficulty,
45) -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
46 literal(name).executes(move |context| set_difficulty(context, difficulty))
47}
48
49#[expect(
50 clippy::unnecessary_wraps,
51 reason = "Command executors use a shared fallible callback signature."
52)]
53fn query_difficulty(
54 context: &SteelCommandContext<CommandSource>,
55) -> Result<i32, CommandSyntaxError> {
56 let difficulty = context.source().world().difficulty();
57 let message = translations::COMMANDS_DIFFICULTY_QUERY
58 .message([TextComponent::from(difficulty_display_name(difficulty))])
59 .component();
60 context.source().send_success(&message, false);
61 Ok(i32::from(u8::from(difficulty)))
62}
63
64fn set_difficulty(
65 context: &SteelCommandContext<CommandSource>,
66 difficulty: Difficulty,
67) -> Result<i32, CommandSyntaxError> {
68 let domain = context.source().world().domain();
69 let worlds = context.source().server().worlds.worlds_in_domain(domain);
70 if worlds.iter().all(|world| world.difficulty() == difficulty) {
71 return Err(CommandSyntaxError::dynamic(
72 translations::COMMANDS_DIFFICULTY_FAILURE
73 .message([TextComponent::from(difficulty_display_name(difficulty))])
74 .component(),
75 ));
76 }
77
78 for world in worlds {
79 world.set_difficulty(difficulty);
80 }
81
82 let message = translations::COMMANDS_DIFFICULTY_SUCCESS
83 .message([TextComponent::from(difficulty_display_name(difficulty))])
84 .component();
85 context.source().send_success(&message, true);
86 Ok(0)
87}
88
89const fn difficulty_display_name(difficulty: Difficulty) -> &'static Translation<0> {
90 match difficulty {
91 Difficulty::Peaceful => &translations::OPTIONS_DIFFICULTY_PEACEFUL,
92 Difficulty::Easy => &translations::OPTIONS_DIFFICULTY_EASY,
93 Difficulty::Normal => &translations::OPTIONS_DIFFICULTY_NORMAL,
94 Difficulty::Hard => &translations::OPTIONS_DIFFICULTY_HARD,
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::difficulty_permission;
101 use crate::permission::{PermissionEntry, PermissionKey, PermissionSet};
102
103 #[test]
104 fn client_difficulty_permission_uses_the_command_root() {
105 let permission = difficulty_permission().expect("permission should build");
106 let allowed = PermissionSet::from_entries([PermissionEntry::allow(
107 PermissionKey::parse("minecraft.command.difficulty")
108 .expect("test permission should parse"),
109 )]);
110 let gamemode_only = PermissionSet::from_entries([PermissionEntry::allow(
111 PermissionKey::parse("minecraft.command.gamemode")
112 .expect("test permission should parse"),
113 )]);
114
115 assert!(allowed.allows(&permission));
116 assert!(!gamemode_only.allows(&permission));
117 }
118}