Skip to main content

steel_core/command/
mod.rs

1//! Brigadier-compatible command parsing, execution, and sender handling.
2
3mod api;
4pub(crate) mod brigadier;
5mod builtins;
6pub(crate) mod execution;
7mod protocol;
8mod queue;
9mod registration;
10pub mod sender;
11pub(crate) mod storage;
12
13pub use api::{
14    CommandArgument, CommandArgumentParser, CommandContext, CommandError, CommandNode,
15    CommandParserSource, CommandReader, CommandReaderCursor, CommandRegistration,
16    CommandRegistrationError, CommandRegistry, CommandSource, CommandSuggestionContext,
17    CommandSuggestions, SuspendedCommand, SuspendedCommandPoll, argument, literal,
18};
19pub use execution::CommandSuspensionOrder;
20
21pub(crate) use builtins::{
22    create_registered_dispatcher, gamemode::handle_client_request, player_can_change_difficulty,
23};
24pub(crate) use protocol::{command_suggestions_packet, command_tree_packet};
25pub use queue::CommandQueueFull;
26pub(crate) use queue::{
27    COMMAND_REQUESTS_PER_TICK, COMMAND_RESUMPTIONS_PER_TICK, CommandRequest, CommandRequestQueue,
28    PendingCommandExecutionQueue,
29};
30
31use steel_utils::entity_events::EntityStatus;
32
33use self::{
34    brigadier::CommandDispatcher as BrigadierCommandDispatcher,
35    execution::{CommandSource as InternalCommandSource, SteelCommandRuntime},
36};
37use crate::command::brigadier::CommandSyntaxError;
38use crate::{player::Player, world::World};
39
40pub(crate) type CommandDispatcher =
41    BrigadierCommandDispatcher<InternalCommandSource, SteelCommandRuntime>;
42
43/// One command completion and its replacement range in UTF-16 code units.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct CommandCompletion {
46    replacement_start: usize,
47    replacement_length: usize,
48    text: String,
49}
50
51impl CommandCompletion {
52    pub(crate) const fn new(
53        replacement_start: usize,
54        replacement_length: usize,
55        text: String,
56    ) -> Self {
57        Self {
58            replacement_start,
59            replacement_length,
60            text,
61        }
62    }
63
64    /// Returns the inclusive replacement start in UTF-16 code units.
65    #[must_use]
66    pub const fn replacement_start(&self) -> usize {
67        self.replacement_start
68    }
69
70    /// Returns the replacement length in UTF-16 code units.
71    #[must_use]
72    pub const fn replacement_length(&self) -> usize {
73        self.replacement_length
74    }
75
76    /// Returns the replacement text.
77    #[must_use]
78    pub fn text(&self) -> &str {
79        &self.text
80    }
81}
82
83/// Projects Steel capabilities onto vanilla's shared gamemaster client affordance.
84/// Packet handlers still authorize game-mode and difficulty requests independently.
85pub(crate) fn client_permission_event(player: &Player, world: &World) -> EntityStatus {
86    client_permission_event_for_capabilities(
87        builtins::gamemode::player_can_use_client_switcher(player, world),
88        builtins::player_can_change_difficulty(player, world),
89    )
90}
91
92const fn client_permission_event_for_capabilities(
93    can_change_game_mode: bool,
94    can_change_difficulty: bool,
95) -> EntityStatus {
96    if can_change_game_mode || can_change_difficulty {
97        EntityStatus::PermissionLevelGamemasters
98    } else {
99        EntityStatus::PermissionLevelAll
100    }
101}
102
103/// Creates a [`CommandSyntaxError`] for a missing argument.
104pub(crate) fn missing_argument(name: &str) -> CommandSyntaxError {
105    CommandSyntaxError::dynamic(format!(
106        "Parsed value for {name} is missing from the command context"
107    ))
108}
109
110/// Creates a [`CommandSyntaxError`] for an argument whose parsed value's type
111/// does not match with that of the argument.
112pub(crate) fn incorrectly_typed_argument(name: &str) -> CommandSyntaxError {
113    CommandSyntaxError::dynamic(format!(
114        "Parsed value for {name} does not match the expected type"
115    ))
116}
117
118#[cfg(test)]
119mod tests {
120    use super::client_permission_event_for_capabilities;
121    use steel_utils::entity_events::EntityStatus;
122
123    #[test]
124    fn gamemaster_projection_enables_either_supported_client_capability() {
125        assert_eq!(
126            client_permission_event_for_capabilities(true, false),
127            EntityStatus::PermissionLevelGamemasters
128        );
129        assert_eq!(
130            client_permission_event_for_capabilities(false, true),
131            EntityStatus::PermissionLevelGamemasters
132        );
133        assert_eq!(
134            client_permission_event_for_capabilities(false, false),
135            EntityStatus::PermissionLevelAll
136        );
137    }
138}