steel_core/command/builtins/
clear.rs1use std::{slice, sync::Arc};
4
5use steel_registry::item_stack::ItemStack;
6use steel_utils::{Identifier, translations};
7use text_components::TextComponent;
8
9use super::super::{
10 brigadier::{ArgumentType, CommandNodeBuilder, CommandSyntaxError},
11 execution::{
12 CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
13 literal,
14 },
15 registration::CommandRegistration,
16};
17use crate::{entity::Entity as _, player::Player};
18
19pub(super) fn registration() -> CommandRegistration<CommandSource> {
20 CommandRegistration::new(Identifier::vanilla_static("clear"), |_| command())
21}
22
23fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
24 literal("clear").executes(clear_self).then(
25 argument("targets", SteelArgumentType::players())
26 .executes(clear_targets)
27 .then(
28 argument("item", SteelArgumentType::item_predicate())
29 .executes(clear_matching)
30 .then(
31 argument("maxCount", ArgumentType::integer(0, i32::MAX))
32 .executes(clear_matching_with_limit),
33 ),
34 ),
35 )
36}
37
38fn clear_self(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
39 let Some(player) = context.source().player() else {
40 return Err(CommandSyntaxError::dynamic(TextComponent::from(
41 &translations::PERMISSIONS_REQUIRES_PLAYER,
42 )));
43 };
44 clear_players(context, slice::from_ref(player), &matches_any_item, -1)
45}
46
47fn clear_targets(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
48 let targets = context.players("targets")?;
49 clear_players(context, &targets, &matches_any_item, -1)
50}
51
52fn clear_matching(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
53 clear_matching_with_count(context, -1)
54}
55
56fn clear_matching_with_limit(
57 context: &SteelCommandContext<CommandSource>,
58) -> Result<i32, CommandSyntaxError> {
59 let max_count = context.integer("maxCount")?;
60 clear_matching_with_count(context, max_count)
61}
62
63fn clear_matching_with_count(
64 context: &SteelCommandContext<CommandSource>,
65 max_count: i32,
66) -> Result<i32, CommandSyntaxError> {
67 let targets = context.players("targets")?;
68 let predicate = context.item_predicate("item")?;
69
70 clear_players(
71 context,
72 &targets,
73 &|stack| predicate.matches(stack),
74 max_count,
75 )
76}
77
78fn clear_players(
79 context: &SteelCommandContext<CommandSource>,
80 targets: &[Arc<Player>],
81 predicate: &dyn Fn(&ItemStack) -> bool,
82 max_count: i32,
83) -> Result<i32, CommandSyntaxError> {
84 let mut count = 0;
85 for target in targets {
86 count += target.clear_or_count_matching_items(predicate, max_count);
87 }
88
89 if count == 0 {
90 let message = if let [target] = targets {
91 translations::CLEAR_FAILED_SINGLE
92 .message([TextComponent::plain(target.plain_text_name())])
93 .component()
94 } else {
95 translations::CLEAR_FAILED_MULTIPLE
96 .message([TextComponent::plain(targets.len().to_string())])
97 .component()
98 };
99 return Err(CommandSyntaxError::dynamic(message));
100 }
101
102 let count_component = TextComponent::plain(count.to_string());
103 let message = if max_count == 0 {
104 if let [target] = targets {
105 translations::COMMANDS_CLEAR_TEST_SINGLE
106 .message([
107 count_component,
108 TextComponent::plain(target.plain_text_name()),
109 ])
110 .component()
111 } else {
112 translations::COMMANDS_CLEAR_TEST_MULTIPLE
113 .message([
114 count_component,
115 TextComponent::plain(targets.len().to_string()),
116 ])
117 .component()
118 }
119 } else if let [target] = targets {
120 translations::COMMANDS_CLEAR_SUCCESS_SINGLE
121 .message([
122 count_component,
123 TextComponent::plain(target.plain_text_name()),
124 ])
125 .component()
126 } else {
127 translations::COMMANDS_CLEAR_SUCCESS_MULTIPLE
128 .message([
129 count_component,
130 TextComponent::plain(targets.len().to_string()),
131 ])
132 .component()
133 };
134 context.source().send_success(&message, true);
135 Ok(count)
136}
137
138const fn matches_any_item(_stack: &ItemStack) -> bool {
139 true
140}
141
142#[cfg(test)]
143mod tests {
144 use steel_registry::init_vanilla_registry;
145
146 use super::super::create_dispatcher;
147 use super::*;
148 use crate::command::brigadier::{CommandDispatcher, NodeId};
149
150 type Dispatcher = CommandDispatcher<CommandSource, SteelCommandRuntime>;
151
152 fn child(dispatcher: &Dispatcher, parent: NodeId, name: &str) -> NodeId {
153 let Some(children) = dispatcher.children(parent) else {
154 panic!("parent node should exist");
155 };
156 let Some(child) = children.iter().copied().find(|child| {
157 dispatcher
158 .node(*child)
159 .is_some_and(|node| node.name() == name)
160 }) else {
161 panic!("child {name} should exist");
162 };
163 child
164 }
165
166 #[test]
167 fn clear_graph_matches_vanilla_argument_shape() {
168 init_vanilla_registry();
169 let Ok(dispatcher) = create_dispatcher() else {
170 panic!("built-in commands should register");
171 };
172 let clear = child(&dispatcher, dispatcher.root(), "clear");
173 let Some(clear_node) = dispatcher.node(clear) else {
174 panic!("clear node should exist");
175 };
176 assert!(clear_node.is_restricted());
177 assert!(clear_node.is_executable());
178
179 let targets = child(&dispatcher, clear, "targets");
180 assert_eq!(
181 dispatcher
182 .node(targets)
183 .and_then(|node| node.argument_type()),
184 Some(&SteelArgumentType::players())
185 );
186 assert!(matches!(
187 dispatcher.node(targets),
188 Some(node) if node.is_executable()
189 ));
190
191 let item = child(&dispatcher, targets, "item");
192 assert_eq!(
193 dispatcher.node(item).and_then(|node| node.argument_type()),
194 Some(&SteelArgumentType::item_predicate())
195 );
196 assert!(matches!(
197 dispatcher.node(item),
198 Some(node) if node.is_executable()
199 ));
200
201 let max_count = child(&dispatcher, item, "maxCount");
202 assert_eq!(
203 dispatcher
204 .node(max_count)
205 .and_then(|node| node.argument_type()),
206 Some(&SteelArgumentType::from(ArgumentType::integer(0, i32::MAX)))
207 );
208 assert!(matches!(
209 dispatcher.node(max_count),
210 Some(node) if node.is_executable()
211 ));
212 }
213}