1use crate::command::brigadier::{
2 ArgumentType, CommandNodeBuilder, CommandSyntaxError, SuggestionsBuilder,
3};
4use crate::command::execution::{
5 CommandSource, SteelArgumentType, SteelCommandRuntime, SteelSuggestionContext, argument,
6 literal, suggest_list,
7};
8use crate::command::registration::CommandRegistration;
9use crate::entity::Entity;
10use crate::player::Player;
11use crate::player::advancement::PlayerAdvancement;
12use std::sync::Arc;
13use steel_registry::REGISTRY;
14use steel_registry::advancement::Advancement;
15use steel_registry::advancement::registry::{AdvancementNode, AdvancementRef};
16use steel_utils::{Identifier, translations};
17use text_components::TextComponent;
18
19pub(super) fn registration() -> CommandRegistration<CommandSource> {
20 CommandRegistration::new(Identifier::vanilla_static("advancement"), |_| command())
21}
22
23fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
24 macro_rules! build_action {
25 ($name:expr, $action:expr) => {
26 literal($name)
27 .then(argument("targets", SteelArgumentType::players())
28 .then(literal("only")
29 .then(
30 argument("advancement", SteelArgumentType::advancement())
31 .executes(
32 |c| {
33 perform_and_show(
34 c.source(),
35 &c.players("targets")?,
36 $action,
37 &get_advancements(
38 c.advancement("advancement")?,
39 Mode::Only,
40 ),
41 )
42 },
43 )
44 .then(
45 argument("criterion", ArgumentType::greedy_string())
46 .suggests(
47 |c: &SteelSuggestionContext<'_, CommandSource>,
48 b: &mut SuggestionsBuilder<'_>| {
49 if let Ok(advancement) = c.advancement("advancement") {
50 suggest_list(b, advancement.criteria_ids());
51 };
52 },
53 )
54 .executes(|c| {
55 perform_criterion(
56 c.source(),
57 &c.players("targets")?,
58 $action,
59 c.advancement("advancement")?,
60 c.string("criterion")?,
61 )
62 }),
63 ),
64 ),
65 )
66 .then(literal("from").then(
67 argument("advancement", SteelArgumentType::advancement()).executes(|c| {
68 perform_and_show(
69 c.source(),
70 &c.players("targets")?,
71 $action,
72 &get_advancements(c.advancement("advancement")?, Mode::From),
73 )
74 }),
75 ))
76 .then(literal("until").then(
77 argument("advancement", SteelArgumentType::advancement()).executes(|c| {
78 perform_and_show(
79 c.source(),
80 &c.players("targets")?,
81 $action,
82 &get_advancements(c.advancement("advancement")?, Mode::Until),
83 )
84 }),
85 ))
86 .then(literal("through").then(
87 argument("advancement", SteelArgumentType::advancement()).executes(|c| {
88 perform_and_show(
89 c.source(),
90 &c.players("targets")?,
91 $action,
92 &get_advancements(c.advancement("advancement")?, Mode::Through),
93 )
94 }),
95 ))
96 .then(literal("everything").executes(|c| {
97 perform(
98 c.source(),
99 &c.players("targets")?,
100 $action,
101 REGISTRY.advancements.advancements(),
102 false,
103 )
104 })))
105 };
106 }
107 literal("advancement")
108 .then(build_action!("grant", Action::Grant))
109 .then(build_action!("revoke", Action::Revoke))
110}
111
112#[derive(Clone, Copy)]
113pub enum Action {
114 Grant,
115 Revoke,
116}
117
118impl Action {
119 fn perform_single_inner(
121 self,
122 player: &Player,
123 guard: &mut PlayerAdvancement,
124 advancement: AdvancementRef,
125 ) -> bool {
126 let progress = guard.progress.get_mut_or_start_progress(advancement);
127 match self {
128 Self::Grant => {
129 if progress.is_done() {
130 return false;
131 }
132 let criteria: Vec<String> = progress
133 .get_remaining_criteria()
134 .map(str::to_owned)
135 .collect();
136 for criterion in criteria {
137 guard.award(player, advancement, &criterion);
138 }
139 true
140 }
141 Self::Revoke => {
142 if !progress.has_progress() {
143 return false;
144 }
145 let criteria: Vec<String> = progress
146 .get_completed_criteria()
147 .map(str::to_owned)
148 .collect();
149 for criterion in criteria {
150 guard.revoke(advancement, &criterion);
151 }
152 true
153 }
154 }
155 }
156
157 fn perform(
158 self,
159 player: &Arc<Player>,
160 advancements: &[AdvancementRef],
161 show_advancement: bool,
162 ) -> i32 {
163 let mut guard = player.advancements.lock();
164 if !show_advancement {
165 guard.flush_dirty(player, true);
166 }
167 let count = advancements
168 .iter()
169 .filter(|advancement| self.perform_single_inner(player, &mut guard, advancement))
170 .count() as i32;
171 if !show_advancement {
172 guard.flush_dirty(player, false);
173 }
174 count
175 }
176
177 fn perform_criterion(
178 self,
179 player: &Arc<Player>,
180 advancement: &'static Advancement,
181 criterion: &str,
182 ) -> bool {
183 let mut guard = player.advancements.lock();
184 match self {
185 Self::Grant => guard.award(player, advancement, criterion),
186 Self::Revoke => guard.revoke(advancement, criterion),
187 }
188 }
189}
190
191#[derive(Clone, Copy)]
192enum Mode {
193 Only,
194 Through,
195 From,
196 Until,
197 #[expect(unused, reason = "to match vanilla")]
198 Everything,
199}
200
201impl Mode {
202 const fn parents(self) -> bool {
203 match self {
204 Self::Only | Self::From => false,
205 Self::Through | Self::Until | Self::Everything => true,
206 }
207 }
208
209 const fn children(self) -> bool {
210 match self {
211 Self::Only | Self::Until => false,
212 Self::Through | Self::From | Self::Everything => true,
213 }
214 }
215}
216
217fn get_advancements(target: AdvancementRef, mode: Mode) -> Vec<AdvancementRef> {
218 let tree = ®ISTRY.advancements;
219 let target_node = tree.by_key(target.key());
220 target_node.map_or_else(
221 || vec![target],
222 |target_node| {
223 let mut advancements = Vec::new();
224 if mode.parents() {
225 let mut parent = target_node.parent();
226 while let Some(parent_id) = parent {
227 let current_node = tree
228 .get_by_idx(parent_id)
229 .expect("unable to get the parent node");
230 advancements.push(current_node.value());
231 parent = current_node.parent();
232 }
233 }
234 advancements.push(target);
235 if mode.children() {
236 add_children(target_node, &mut advancements);
237 }
238 advancements
239 },
240 )
241}
242
243fn add_children(parent: &AdvancementNode, output: &mut Vec<&Advancement>) {
244 for child in parent.children() {
245 let node = REGISTRY
246 .advancements
247 .get_by_idx(child)
248 .expect("invalid child index in the parent node");
249 output.push(node.value());
250 add_children(node, output);
251 }
252}
253
254#[inline]
255fn perform_and_show(
256 context: &CommandSource,
257 players: &[Arc<Player>],
258 action: Action,
259 advancements: &[AdvancementRef],
260) -> Result<i32, CommandSyntaxError> {
261 perform(context, players, action, advancements, true)
262}
263
264#[inline]
265fn perform_error_generator(
266 targets: &[Arc<Player>],
267 action: Action,
268 advancements: &[AdvancementRef],
269) -> Result<i32, CommandSyntaxError> {
270 if let [first_advancement] = advancements[..] {
271 if let [first_player] = targets {
272 Err(CommandSyntaxError::dynamic(
273 match action {
274 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_ONE_TO_ONE_FAILURE,
275 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_ONE_TO_ONE_FAILURE,
276 }
277 .message([first_advancement.name(), first_player.display_name()]),
278 ))
279 } else {
280 Err(CommandSyntaxError::dynamic(
281 match action {
282 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_ONE_TO_MANY_FAILURE,
283 Action::Revoke => {
284 &translations::COMMANDS_ADVANCEMENT_REVOKE_ONE_TO_MANY_FAILURE
285 }
286 }
287 .message([first_advancement.name(), targets.len().to_string().into()]),
288 ))
289 }
290 } else if let [first_player] = targets {
291 Err(CommandSyntaxError::dynamic(
292 match action {
293 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_MANY_TO_ONE_FAILURE,
294 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_MANY_TO_ONE_FAILURE,
295 }
296 .message([
297 advancements.len().to_string().into(),
298 first_player.display_name(),
299 ]),
300 ))
301 } else {
302 Err(CommandSyntaxError::dynamic(
303 match action {
304 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_MANY_TO_MANY_FAILURE,
305 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_MANY_TO_MANY_FAILURE,
306 }
307 .message([
308 TextComponent::from(advancements.len().to_string()),
309 TextComponent::from(targets.len().to_string()),
310 ]),
311 ))
312 }
313}
314fn perform(
315 context: &CommandSource,
316 targets: &[Arc<Player>],
317 action: Action,
318 advancements: &[AdvancementRef],
319 show_advancement: bool,
320) -> Result<i32, CommandSyntaxError> {
321 let mut player_count = 0;
322 let mut advancement_count = 0;
323 let mut players = Vec::new();
324 for player in targets {
325 let count = action.perform(player, advancements, show_advancement);
326 if count > 0 {
327 player_count += 1;
328 players.push(player);
329 }
330 advancement_count += count;
331 }
332 if advancement_count == 0 {
333 return perform_error_generator(targets, action, advancements);
334 }
335 let translate = if let [first_advancement] = advancements[..] {
336 if let [first_player] = players[..] {
337 match action {
338 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_ONE_TO_ONE_SUCCESS,
339 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_ONE_TO_ONE_SUCCESS,
340 }
341 .message([first_advancement.name(), first_player.display_name()])
342 .component()
343 } else {
344 match action {
345 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_ONE_TO_MANY_SUCCESS,
346 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_ONE_TO_MANY_SUCCESS,
347 }
348 .message([first_advancement.name(), player_count.to_string().into()])
349 .component()
350 }
351 } else if let [first_player] = players[..] {
352 match action {
353 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_MANY_TO_ONE_SUCCESS,
354 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_MANY_TO_ONE_SUCCESS,
355 }
356 .message([
357 advancements.len().to_string().into(),
358 first_player.display_name(),
359 ])
360 .component()
361 } else {
362 match action {
363 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_MANY_TO_MANY_SUCCESS,
364 Action::Revoke => &translations::COMMANDS_ADVANCEMENT_REVOKE_MANY_TO_MANY_SUCCESS,
365 }
366 .message([
367 TextComponent::from(advancements.len().to_string()),
368 TextComponent::from(player_count.to_string()),
369 ])
370 .component()
371 };
372 context.send_success(&translate, true);
373 Ok(advancement_count)
374}
375
376pub fn perform_criterion(
377 context: &CommandSource,
378 targets: &[Arc<Player>],
379 action: Action,
380 advancement: AdvancementRef,
381 criterion: &str,
382) -> Result<i32, CommandSyntaxError> {
383 if !advancement.has_criterion(criterion) {
384 return Err(CommandSyntaxError::dynamic(
385 translations::COMMANDS_ADVANCEMENT_CRITERION_NOT_FOUND
386 .message([advancement.name(), criterion.to_owned().into()]),
387 ));
388 }
389
390 let changed_targets = targets
391 .iter()
392 .filter(|player| action.perform_criterion(player, advancement, criterion))
393 .collect::<Vec<_>>();
394 let player_count = changed_targets.len() as i32;
395
396 if player_count == 0 {
397 if let [first_player] = targets {
398 Err(CommandSyntaxError::dynamic(
399 match action {
400 Action::Grant => {
401 &translations::COMMANDS_ADVANCEMENT_GRANT_CRITERION_TO_ONE_FAILURE
402 }
403 Action::Revoke => {
404 &translations::COMMANDS_ADVANCEMENT_REVOKE_CRITERION_TO_ONE_FAILURE
405 }
406 }
407 .message([
408 criterion.to_owned().into(),
409 advancement.name(),
410 first_player.display_name(),
411 ]),
412 ))
413 } else {
414 Err(CommandSyntaxError::dynamic(
415 match action {
416 Action::Grant => {
417 &translations::COMMANDS_ADVANCEMENT_GRANT_CRITERION_TO_MANY_FAILURE
418 }
419 Action::Revoke => {
420 &translations::COMMANDS_ADVANCEMENT_REVOKE_CRITERION_TO_MANY_FAILURE
421 }
422 }
423 .message([
424 criterion.to_owned().into(),
425 advancement.name(),
426 targets.len().to_string().into(),
427 ]),
428 ))
429 }
430 } else {
431 let targets = changed_targets.as_slice();
432 let translate = if let [first_player] = targets {
433 match action {
434 Action::Grant => &translations::COMMANDS_ADVANCEMENT_GRANT_CRITERION_TO_ONE_SUCCESS,
435 Action::Revoke => {
436 &translations::COMMANDS_ADVANCEMENT_REVOKE_CRITERION_TO_ONE_SUCCESS
437 }
438 }
439 .message([
440 criterion.to_owned().into(),
441 advancement.name(),
442 first_player.display_name(),
443 ])
444 .component()
445 } else {
446 match action {
447 Action::Grant => {
448 &translations::COMMANDS_ADVANCEMENT_GRANT_CRITERION_TO_MANY_SUCCESS
449 }
450 Action::Revoke => {
451 &translations::COMMANDS_ADVANCEMENT_REVOKE_CRITERION_TO_MANY_SUCCESS
452 }
453 }
454 .message([
455 criterion.to_owned().into(),
456 advancement.name(),
457 player_count.to_string().into(),
458 ])
459 .component()
460 };
461 context.send_success(&translate, true);
462 Ok(player_count)
463 }
464}