Skip to main content

steel_core/command/builtins/
defaultgamemode.rs

1//! Vanilla `/defaultgamemode` command setting the default game mode for a domain's worlds.
2
3use steel_utils::{Identifier, translations};
4use text_components::TextComponent;
5
6use super::super::{
7    brigadier::{CommandNodeBuilder, CommandSyntaxError},
8    execution::{
9        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
10        literal,
11    },
12    registration::CommandRegistration,
13};
14use super::gamemode::game_mode_translation;
15
16pub(super) fn registration() -> CommandRegistration<CommandSource> {
17    CommandRegistration::new(Identifier::vanilla_static("defaultgamemode"), |_| command())
18}
19
20fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
21    literal("defaultgamemode").then(
22        argument("gamemode", SteelArgumentType::game_mode())
23            .executes(set_default_game_mode)
24            .then(
25                argument("domain", SteelArgumentType::domain())
26                    .executes(set_default_game_mode_domain),
27            ),
28    )
29}
30
31fn set_default_game_mode(
32    context: &SteelCommandContext<CommandSource>,
33) -> Result<i32, CommandSyntaxError> {
34    let domain = context.source().world().domain().to_owned();
35    apply_default_game_mode(context, &domain)
36}
37
38fn set_default_game_mode_domain(
39    context: &SteelCommandContext<CommandSource>,
40) -> Result<i32, CommandSyntaxError> {
41    let domain = context.domain("domain")?;
42    apply_default_game_mode(context, domain)
43}
44
45fn apply_default_game_mode(
46    context: &SteelCommandContext<CommandSource>,
47    domain: &str,
48) -> Result<i32, CommandSyntaxError> {
49    if !context.source().allows_execution_domain(domain) {
50        return Err(CommandSyntaxError::dynamic(
51            "Players cannot execute commands across Steel domains",
52        ));
53    }
54
55    let game_mode = context.game_mode("gamemode")?;
56    let worlds = context.source().server().worlds.worlds_in_domain(domain);
57
58    for world in worlds {
59        world.set_default_gamemode(game_mode);
60    }
61
62    // TODO: Enforce the new game mode on online players when `force-gamemode=true` and return the
63    // count of updated players. This also needs to apply when saved players join, requiring a shared
64    // `force-gamemode` foundation.
65
66    let message = translations::COMMANDS_DEFAULTGAMEMODE_SUCCESS
67        .message([TextComponent::from(game_mode_translation(game_mode))])
68        .component();
69    context.source().send_success(&message, true);
70    Ok(0)
71}
72
73#[cfg(test)]
74mod tests {
75    use steel_registry::init_vanilla_registry;
76
77    use super::super::create_dispatcher;
78    use crate::command::execution::SteelArgumentType;
79
80    #[test]
81    fn defaultgamemode_graph_matches_shape() {
82        init_vanilla_registry();
83        let Ok(dispatcher) = create_dispatcher() else {
84            panic!("built-in commands should register");
85        };
86        let Some(root) = dispatcher.children(dispatcher.root()).and_then(|children| {
87            children.iter().copied().find(|child| {
88                dispatcher
89                    .node(*child)
90                    .is_some_and(|node| node.name() == "defaultgamemode")
91            })
92        }) else {
93            panic!("defaultgamemode root should exist");
94        };
95        let Some(game_mode) = dispatcher
96            .children(root)
97            .and_then(|children| children.first())
98            .copied()
99        else {
100            panic!("gamemode argument should exist");
101        };
102        assert!(matches!(
103            dispatcher.node(game_mode),
104            Some(node)
105                if node.is_executable()
106                    && node.argument_type() == Some(&SteelArgumentType::game_mode())
107        ));
108        let Some(domain) = dispatcher
109            .children(game_mode)
110            .and_then(|children| children.first())
111            .copied()
112        else {
113            panic!("domain argument should exist");
114        };
115        assert!(matches!(
116            dispatcher.node(domain),
117            Some(node)
118                if node.is_executable()
119                    && node.argument_type() == Some(&SteelArgumentType::domain())
120        ));
121    }
122}