steel_core/behavior/blocks/redstone/
target_block.rs1use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::BlockStateProperties;
9use steel_utils::axis::Axis;
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::{BlockBehavior, BlockPlaceContext};
14use crate::entity::Entity;
15use crate::entity::projectile::Projectile;
16use crate::world::{ClipHitResult, LevelReader, SignalQueryContext, World};
17
18const ACTIVATION_TICKS_ARROWS: i32 = 20;
19const ACTIVATION_TICKS_OTHER: i32 = 8;
20const RESET_ON_PLACE_FLAGS: UpdateFlags =
21 UpdateFlags::UPDATE_CLIENTS.union(UpdateFlags::UPDATE_KNOWN_SHAPE);
22
23#[block_behavior]
25pub struct TargetBlock {
26 block: BlockRef,
27}
28
29impl TargetBlock {
30 #[must_use]
32 pub const fn new(block: BlockRef) -> Self {
33 Self { block }
34 }
35
36 fn distance_from_block_center(coordinate: f64) -> f64 {
37 (coordinate - coordinate.floor() - 0.5).abs()
38 }
39
40 fn redstone_strength(hit: &ClipHitResult) -> i32 {
41 let dist_x = Self::distance_from_block_center(hit.location.x);
42 let dist_y = Self::distance_from_block_center(hit.location.y);
43 let dist_z = Self::distance_from_block_center(hit.location.z);
44 let distance = match hit.direction.axis() {
45 Axis::Y => dist_x.max(dist_z),
46 Axis::Z => dist_x.max(dist_y),
47 Axis::X => dist_y.max(dist_z),
48 };
49 let centered = ((0.5 - distance) / 0.5).clamp(0.0, 1.0);
50 (15.0 * centered).ceil().max(1.0) as i32
51 }
52
53 fn update_redstone_output(
54 &self,
55 world: &Arc<World>,
56 state: BlockStateId,
57 hit: &ClipHitResult,
58 entity: &dyn Entity,
59 ) -> i32 {
60 let strength = Self::redstone_strength(hit);
61 if !world.has_scheduled_block_tick(hit.block_pos, self.block) {
62 world.set_block(
63 hit.block_pos,
64 state.set_value(&BlockStateProperties::POWER, strength as u8),
65 UpdateFlags::UPDATE_ALL,
66 );
67 world.schedule_block_tick_default(
68 hit.block_pos,
69 self.block,
70 if entity.is_abstract_arrow() {
71 ACTIVATION_TICKS_ARROWS
72 } else {
73 ACTIVATION_TICKS_OTHER
74 },
75 );
76 }
77 strength
78 }
79}
80
81impl BlockBehavior for TargetBlock {
82 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
83 Some(self.block.default_state())
84 }
85
86 fn on_projectile_hit(
87 &self,
88 state: BlockStateId,
89 world: &Arc<World>,
90 hit: &ClipHitResult,
91 projectile: &dyn Projectile,
92 ) {
93 let _strength = self.update_redstone_output(world, state, hit, projectile);
94 }
97
98 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
99 if state.get_value(&BlockStateProperties::POWER) != 0 {
100 world.set_block(
101 pos,
102 state.set_value(&BlockStateProperties::POWER, 0_u8),
103 UpdateFlags::UPDATE_ALL,
104 );
105 }
106 }
107
108 fn on_place(
109 &self,
110 state: BlockStateId,
111 world: &Arc<World>,
112 pos: BlockPos,
113 old_state: BlockStateId,
114 _moved_by_piston: bool,
115 ) {
116 if old_state.get_block() != state.get_block()
117 && state.get_value(&BlockStateProperties::POWER) > 0
118 && !world.has_scheduled_block_tick(pos, self.block)
119 {
120 world.set_block(
121 pos,
122 state.set_value(&BlockStateProperties::POWER, 0_u8),
123 RESET_ON_PLACE_FLAGS,
124 );
125 }
126 }
127
128 fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
129 true
130 }
131
132 fn get_own_signal(
133 &self,
134 state: BlockStateId,
135 _world: &dyn LevelReader,
136 _pos: BlockPos,
137 _context: SignalQueryContext,
138 ) -> i32 {
139 i32::from(state.get_value(&BlockStateProperties::POWER))
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use glam::DVec3;
146 use steel_registry::blocks::properties::Direction;
147 use steel_registry::init_vanilla_registry;
148
149 use super::*;
150
151 fn hit(location: DVec3, direction: Direction) -> ClipHitResult {
152 ClipHitResult {
153 location,
154 direction,
155 block_pos: BlockPos::new(-1, 64, -1),
156 miss: false,
157 inside: false,
158 world_border_hit: false,
159 }
160 }
161
162 #[test]
163 fn hit_strength_uses_the_hit_face_and_floor_based_fraction() {
164 init_vanilla_registry();
165
166 assert_eq!(
167 TargetBlock::redstone_strength(&hit(DVec3::new(-0.5, 64.99, -0.5), Direction::Up,)),
168 15
169 );
170 assert_eq!(
171 TargetBlock::redstone_strength(&hit(DVec3::new(-0.01, 64.5, -0.5), Direction::Up,)),
172 1
173 );
174 assert_eq!(
175 TargetBlock::redstone_strength(&hit(DVec3::new(-0.5, 64.5, -0.25), Direction::North,)),
176 15
177 );
178 }
179}