steel_core/player/game_mode/
adventure_mode.rs1use 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 #[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#[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#[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}