Skip to main content

steel_core/command/builtins/
advancement.rs

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    /// inner function that directly take the locked [`PlayerAdvancement`]
120    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 = &REGISTRY.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}