Skip to main content

steel_core/player/game_mode/
adventure_mode.rs

1//! Vanilla adventuremode item component checks
2
3use steel_registry::{
4    blocks::properties::Direction,
5    data_components::{
6        AdventureModePredicate, BlockPredicate,
7        vanilla_components::{CAN_BREAK, CAN_PLACE_ON},
8    },
9    item_stack::ItemStack,
10};
11use steel_utils::{BlockPos, nbt::compare_nbt_compounds};
12
13use crate::{player::Player, world::World};
14
15impl Player {
16    /// Can the player use the given item at the given position in the given direction?
17    #[must_use]
18    pub(crate) fn may_use_item_at(
19        &self,
20        pos: BlockPos,
21        direction: Direction,
22        item: &ItemStack,
23    ) -> bool {
24        if self.abilities.lock().may_build {
25            return true;
26        }
27
28        let world = self.get_world();
29        can_place_on(item, &world, direction.opposite().relative(pos))
30    }
31}
32
33/// Is it breakable=
34#[must_use]
35pub(crate) fn can_break(item: &ItemStack, world: &World, pos: BlockPos) -> bool {
36    item.get(CAN_BREAK)
37        .is_some_and(|predicate| matches(predicate, world, pos))
38}
39
40/// Can you place it on something?
41#[must_use]
42pub(super) fn can_place_on(item: &ItemStack, world: &World, pos: BlockPos) -> bool {
43    item.get(CAN_PLACE_ON)
44        .is_some_and(|predicate| matches(predicate, world, pos))
45}
46
47fn matches(predicate: &AdventureModePredicate, world: &World, pos: BlockPos) -> bool {
48    predicate
49        .predicates()
50        .iter()
51        .any(|predicate| block_predicate_matches(predicate, world, pos))
52}
53
54fn block_predicate_matches(predicate: &BlockPredicate, world: &World, pos: BlockPos) -> bool {
55    if !predicate.matches_state(world.get_block_state(pos)) {
56        return false;
57    }
58
59    let Some(expected_nbt) = predicate.nbt() else {
60        return true;
61    };
62    let Some(block_entity) = world.get_block_entity(pos) else {
63        return false;
64    };
65
66    let actual_nbt = block_entity.save_with_full_metadata();
67    compare_nbt_compounds(expected_nbt.tag(), &actual_nbt, true)
68}
69
70#[cfg(test)]
71mod tests {
72    use std::sync::Arc;
73
74    use steel_registry::{
75        RegistryHolderSet,
76        blocks::properties::Direction,
77        data_component_predicate::{DataComponentExactPredicate, DataComponentMatchers},
78        data_components::{
79            AdventureModePredicate, BlockPredicate, DataComponentMap,
80            vanilla_components::{CAN_BREAK, CAN_PLACE_ON, DAMAGE},
81        },
82        init_vanilla_registry,
83        item_predicate::{
84            StatePropertiesPredicate, StatePropertyMatcher, StatePropertyValueMatcher,
85        },
86        item_stack::ItemStack,
87        vanilla_blocks, vanilla_items,
88    };
89    use steel_utils::{
90        BlockPos, ChunkPos,
91        types::{GameType, UpdateFlags},
92    };
93
94    use crate::{
95        behavior::init_behaviors,
96        test_support::{TestPlayerBuilder, fresh_test_world, insert_ready_full_chunk},
97    };
98
99    use super::can_break;
100
101    #[test]
102    fn may_use_item_at_tests_the_block_behind_the_placement_face() {
103        init_vanilla_registry();
104        init_behaviors();
105        let world_fixture = fresh_test_world("adventure_mode_may_use_item_at");
106        let world = &world_fixture.world;
107        let clicked_pos = BlockPos::new(1, 64, 0);
108        insert_ready_full_chunk(world, ChunkPos::from_block_pos(clicked_pos));
109        assert!(world.set_block(
110            clicked_pos,
111            vanilla_blocks::STONE.default_state(),
112            UpdateFlags::UPDATE_ALL,
113        ));
114
115        let player = TestPlayerBuilder::new(Arc::clone(world), "TestPlayer", 1).build();
116        player.restore_game_modes(GameType::Adventure, None);
117        player
118            .abilities
119            .lock()
120            .update_for_game_mode(GameType::Adventure);
121
122        let placement_pos = clicked_pos.above();
123        let plain_bucket = ItemStack::new(&vanilla_items::WATER_BUCKET);
124        assert!(!player.may_use_item_at(placement_pos, Direction::Up, &plain_bucket));
125
126        let predicate = BlockPredicate::new(
127            Some(RegistryHolderSet::Direct(vec![&vanilla_blocks::STONE])),
128            None,
129            None,
130            DataComponentMatchers::ANY,
131        );
132        let mut allowed_bucket = plain_bucket.clone();
133        allowed_bucket.set(
134            CAN_PLACE_ON,
135            AdventureModePredicate::new(vec![predicate]).expect("one block predicate is valid"),
136        );
137        assert!(player.may_use_item_at(placement_pos, Direction::Up, &allowed_bucket));
138
139        player.abilities.lock().may_build = true;
140        assert!(player.may_use_item_at(placement_pos, Direction::Up, &plain_bucket));
141    }
142
143    #[test]
144    fn block_in_world_predicates_ignore_component_matchers() {
145        init_vanilla_registry();
146        init_behaviors();
147        let world_fixture = fresh_test_world("adventure_mode_ignores_component_matchers");
148        let world = &world_fixture.world;
149        let pos = BlockPos::new(1, 64, 0);
150        insert_ready_full_chunk(world, ChunkPos::from_block_pos(pos));
151        assert!(world.set_block(
152            pos,
153            vanilla_blocks::OAK_LOG.default_state(),
154            UpdateFlags::UPDATE_ALL,
155        ));
156
157        let state_properties = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
158            "axis".to_owned(),
159            StatePropertyValueMatcher::Exact("y".to_owned()),
160        )])
161        .expect("one state property has a unique name");
162        let mut expected_components = DataComponentMap::new();
163        expected_components.set(DAMAGE, Some(1));
164        let component_matchers = DataComponentMatchers::new(
165            DataComponentExactPredicate::all_of(&expected_components)
166                .expect("damage is persistable"),
167            Vec::new(),
168        )
169        .expect("one exact matcher is valid");
170        let predicate = BlockPredicate::new(
171            Some(RegistryHolderSet::Direct(vec![&vanilla_blocks::OAK_LOG])),
172            Some(state_properties),
173            None,
174            component_matchers,
175        );
176        let mut item = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
177        item.set(
178            CAN_BREAK,
179            AdventureModePredicate::new(vec![predicate]).expect("one block predicate is valid"),
180        );
181
182        assert!(can_break(&item, world, pos));
183    }
184}