Skip to main content

steel_core/player/advancement/
visibility_evaluator.rs

1use crate::player::advancement::PlayerAdvancement;
2use steel_registry::REGISTRY;
3use steel_registry::advancement::registry::{AdvancementNodeRef, AdvancementRef};
4
5static VISIBILITY_DEPTH: usize = 3;
6
7fn evaluate_visibility_rule(advancement: AdvancementRef, is_done: bool) -> VisibilityRule {
8    let display = advancement.display();
9    display.map_or(VisibilityRule::Hide, |display| {
10        if is_done {
11            VisibilityRule::Show
12        } else if display.hidden {
13            VisibilityRule::Hide
14        } else {
15            VisibilityRule::NoChange
16        }
17    })
18}
19
20/// `ascendants` variable is working like a stack so this is why we do `.rev().take(VISIBILITY_DEPTH)`
21fn evaluate_visibility_for_unfinished_node(ascendants: &[VisibilityRule]) -> bool {
22    for visibility in ascendants.iter().rev().take(VISIBILITY_DEPTH) {
23        match visibility {
24            VisibilityRule::Show => return true,
25            VisibilityRule::Hide => return false,
26            VisibilityRule::NoChange => {}
27        }
28    }
29    false
30}
31
32/// Recursively evaluates the visibility of an advancement and all of its
33/// descendants.
34///
35/// The current node's completion state and display visibility rule are added
36/// to `ascendants` while its children are evaluated. Once all descendants
37/// have been processed, the node is visible if it or any descendant is
38/// complete, or if the inherited visibility rules allow unfinished nodes to
39/// remain visible.
40///
41/// `is_done_test` determines whether each advancement is complete, while
42/// `output` receives the final visibility result for each visited node.
43///
44/// Returns `true` if this node or any of its descendants is complete.
45pub(super) fn evaluate_visibility_with_rules(
46    node: AdvancementNodeRef,
47    player_advancement: &mut PlayerAdvancement,
48    ascendants: &mut Vec<VisibilityRule>,
49    is_done_test: &mut impl FnMut(&mut PlayerAdvancement, AdvancementNodeRef) -> bool,
50    output: &mut impl FnMut(&mut PlayerAdvancement, AdvancementNodeRef, bool),
51) -> bool {
52    let tree = REGISTRY.advancements.nodes();
53    let is_self_done = is_done_test(player_advancement, node);
54    let descendant_visibility = evaluate_visibility_rule(node.value(), is_self_done);
55    let mut is_self_or_descendant_done = is_self_done;
56    ascendants.push(descendant_visibility);
57
58    for child in node.children() {
59        is_self_or_descendant_done |= evaluate_visibility_with_rules(
60            &tree[child],
61            player_advancement,
62            ascendants,
63            is_done_test,
64            output,
65        );
66    }
67
68    let visibility =
69        is_self_or_descendant_done || evaluate_visibility_for_unfinished_node(ascendants);
70    ascendants.pop();
71    output(player_advancement, node, visibility);
72    is_self_or_descendant_done
73}
74
75pub(crate) fn evaluate_visibility(
76    node: AdvancementNodeRef,
77    player_advancement: &mut PlayerAdvancement,
78    is_done: &mut impl FnMut(&mut PlayerAdvancement, AdvancementNodeRef) -> bool,
79    output: &mut impl FnMut(&mut PlayerAdvancement, AdvancementNodeRef, bool),
80) -> bool {
81    let root = REGISTRY.advancements.root(node);
82    let mut visibility_stack: Vec<VisibilityRule> = Vec::new();
83    evaluate_visibility_with_rules(
84        root,
85        player_advancement,
86        &mut visibility_stack,
87        is_done,
88        output,
89    )
90}
91
92#[derive(PartialEq, Eq, Copy, Clone)]
93pub(super) enum VisibilityRule {
94    Show,
95    Hide,
96    NoChange,
97}