steel_core/command/execution/
profile.rs1use steel_protocol::packets::game::{
4 ArgumentType as ProtocolArgumentType, SuggestionType as ProtocolSuggestionType,
5};
6use steel_utils::{DowncastType, DowncastTypeKey, translations};
7use text_components::TextComponent;
8use uuid::Uuid;
9
10use crate::command::brigadier::{
11 CommandSyntaxError, CommandSyntaxErrorKind, StringReader, SuggestionsBuilder,
12};
13
14use super::{
15 CommandArgumentSource, CommandSource,
16 argument::{SteelArgumentParser, SteelArgumentSuggestionContext},
17 selector::{EntitySelector, parse_entity_selector, suggest_entity_selector},
18};
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub(crate) struct ResolvedGameProfile {
23 pub(crate) uuid: Uuid,
24 pub(crate) name: String,
25}
26
27#[derive(Clone, Debug, PartialEq)]
29pub(crate) enum GameProfileArgument {
30 Selector(Box<EntitySelector>),
32 Direct(Box<str>),
34}
35
36impl GameProfileArgument {
37 pub(crate) async fn resolve(
38 self,
39 source: &CommandSource,
40 ) -> Result<Vec<ResolvedGameProfile>, CommandSyntaxError> {
41 match self {
42 Self::Selector(selector) => {
43 let players = selector.find_online_profile_players(source)?;
44 if players.is_empty() {
45 return Err(CommandSyntaxError::dynamic(TextComponent::from(
46 &translations::ARGUMENT_ENTITY_NOTFOUND_PLAYER,
47 )));
48 }
49 Ok(players
50 .into_iter()
51 .map(|player| ResolvedGameProfile {
52 uuid: player.gameprofile.id,
53 name: player.gameprofile.name.clone(),
54 })
55 .collect())
56 }
57 Self::Direct(name) => {
58 let profile = source
59 .server()
60 .resolve_player_profile(&name)
61 .await
62 .map_err(|error| CommandSyntaxError::dynamic(error.to_string()))?;
63 Ok(vec![ResolvedGameProfile {
64 uuid: profile.uuid(),
65 name: profile.last_known_name().to_owned(),
66 }])
67 }
68 }
69 }
70}
71
72unsafe impl DowncastType for GameProfileArgument {
74 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:command/value/game_profile");
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub(super) enum GameProfileSuggestionMode {
79 All,
80 NonOperators,
81 Operators,
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub(super) struct GameProfileParser {
86 suggestion_mode: GameProfileSuggestionMode,
87}
88
89impl GameProfileParser {
90 pub(super) const fn new(suggestion_mode: GameProfileSuggestionMode) -> Self {
91 Self { suggestion_mode }
92 }
93}
94
95unsafe impl DowncastType for GameProfileParser {
97 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:command/parser/game_profile");
98}
99
100impl SteelArgumentParser for GameProfileParser {
101 type Value = GameProfileArgument;
102
103 fn parse(
104 &self,
105 reader: &mut StringReader<'_>,
106 source: &dyn CommandArgumentSource,
107 ) -> Result<Self::Value, CommandSyntaxError> {
108 if reader.peek() == Some('@') {
109 return parse_entity_selector(reader, source, false, true)
110 .map(Box::new)
111 .map(GameProfileArgument::Selector);
112 }
113
114 let value = reader.read_unquoted_string();
115 if value.is_empty() {
116 return Err(reader.error(CommandSyntaxErrorKind::UnknownArgument));
117 }
118 Ok(GameProfileArgument::Direct(value.into()))
119 }
120
121 fn list_suggestions(
122 &self,
123 context: &dyn SteelArgumentSuggestionContext,
124 builder: &mut SuggestionsBuilder<'_>,
125 ) {
126 suggest_entity_selector(builder, context.source(), false, true);
127 let names = match self.suggestion_mode {
128 GameProfileSuggestionMode::All => context.source().all_profile_names(),
129 GameProfileSuggestionMode::NonOperators => {
130 context.source().non_operator_profile_names()
131 }
132 GameProfileSuggestionMode::Operators => context.source().operator_profile_names(),
133 };
134 let prefix = builder.remaining_lowercase().to_owned();
135 for name in names {
136 if name.to_lowercase().starts_with(&prefix) {
137 builder.suggest(name);
138 }
139 }
140 }
141
142 fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
143 (
144 ProtocolArgumentType::GameProfile,
145 Some(ProtocolSuggestionType::AskServer),
146 )
147 }
148}