Skip to main content

steel_core/behavior/blocks/decoration/
jukebox_block.rs

1//! Vanilla jukebox block behavior.
2
3use std::sync::{Arc, Weak};
4
5use steel_macros::block_behavior;
6use steel_registry::block_entity_type::BlockEntityTypeRef;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt as _;
9use steel_registry::blocks::properties::{BlockStateProperties, Direction};
10use steel_registry::data_components::vanilla_components;
11use steel_registry::{vanilla_block_entity_types, vanilla_custom_stats, vanilla_game_events};
12use steel_utils::types::{InteractionHand, UpdateFlags};
13use steel_utils::{BlockPos, BlockStateId, Downcast as _};
14
15use crate::behavior::blocks::{MAX_REDSTONE_SIGNAL, MIN_REDSTONE_SIGNAL};
16use crate::behavior::{
17    BlockBehavior, BlockEntityCreation, BlockHitResult, BlockPlaceContext, InteractionResult,
18    InventoryAccess, PlacementSource,
19};
20use crate::block_entity::entities::JukeboxBlockEntity;
21use crate::block_entity::{BLOCK_ENTITIES, BlockEntityTicker};
22use crate::entity::Entity;
23use crate::player::Player;
24use crate::world::game_event::GameEventContext;
25use crate::world::{LevelReader, SignalQueryContext, World};
26
27/// Vanilla `JukeboxBlock` behavior.
28#[block_behavior]
29pub struct JukeboxBlock {
30    block: BlockRef,
31}
32
33impl JukeboxBlock {
34    /// Creates jukebox behavior for `block`.
35    #[must_use]
36    pub const fn new(block: BlockRef) -> Self {
37        Self { block }
38    }
39}
40
41impl BlockBehavior for JukeboxBlock {
42    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
43        Some(self.block.default_state())
44    }
45
46    fn set_placed_by(
47        &self,
48        state: BlockStateId,
49        world: &Arc<World>,
50        pos: BlockPos,
51        source: &PlacementSource<'_>,
52    ) {
53        let (block_entity_data, has_record) = source.with_item(|stack| {
54            let Some(data) = stack.get(vanilla_components::BLOCK_ENTITY_DATA) else {
55                return (None, false);
56            };
57            let has_record = data.data().as_compound().get("RecordItem").is_some();
58            (
59                Some((data.block_entity_type(), data.data().copy_tag())),
60                has_record,
61            )
62        });
63
64        if let Some((block_entity_type, data)) = block_entity_data
65            && block_entity_type == &vanilla_block_entity_types::JUKEBOX
66            && let Some(block_entity) = world.get_block_entity(pos)
67            && let Some(jukebox) = block_entity.downcast_ref::<JukeboxBlockEntity>()
68        {
69            jukebox.apply_item_block_entity_data(data);
70        }
71
72        // Vanilla derives this flag from the raw placement data, even when its
73        // declared block-entity type does not match the placed jukebox.
74        if has_record {
75            world.set_block(
76                pos,
77                state.set_value(&BlockStateProperties::HAS_RECORD, true),
78                UpdateFlags::UPDATE_CLIENTS,
79            );
80        }
81    }
82
83    fn affect_neighbors_after_removal(
84        &self,
85        _state: BlockStateId,
86        world: &Arc<World>,
87        pos: BlockPos,
88        _moved_by_piston: bool,
89    ) {
90        world.update_neighbor_for_output_signal(pos, self.block);
91    }
92
93    fn use_item_on(
94        &self,
95        state: BlockStateId,
96        world: &Arc<World>,
97        pos: BlockPos,
98        player: &Player,
99        _hand: InteractionHand,
100        _hit_result: &BlockHitResult,
101        inv: &mut InventoryAccess,
102    ) -> InteractionResult {
103        if state.get_value(&BlockStateProperties::HAS_RECORD) {
104            return InteractionResult::TryEmptyHandInteraction;
105        }
106
107        let record = inv.with_item(|stack| {
108            stack.get(vanilla_components::JUKEBOX_PLAYABLE)?;
109            Some(if player.has_infinite_materials() {
110                stack.copy_with_count(1)
111            } else {
112                stack.split(1)
113            })
114        });
115        let Some(record) = record else {
116            return InteractionResult::TryEmptyHandInteraction;
117        };
118
119        if let Some(block_entity) = world.get_block_entity(pos)
120            && let Some(jukebox) = block_entity.downcast_ref::<JukeboxBlockEntity>()
121        {
122            jukebox.set_the_item(record);
123            world.game_event(
124                &vanilla_game_events::BLOCK_CHANGE,
125                pos,
126                &GameEventContext::new(Some(player as &dyn Entity), Some(state)),
127            );
128        }
129        player.award_custom_stat(&vanilla_custom_stats::PLAY_RECORD);
130        InteractionResult::Success
131    }
132
133    fn use_without_item(
134        &self,
135        state: BlockStateId,
136        world: &Arc<World>,
137        pos: BlockPos,
138        _player: &Player,
139        _hit_result: &BlockHitResult,
140        _inv: &mut InventoryAccess,
141    ) -> InteractionResult {
142        if !state.get_value(&BlockStateProperties::HAS_RECORD) {
143            return InteractionResult::Pass;
144        }
145        let Some(block_entity) = world.get_block_entity(pos) else {
146            return InteractionResult::Pass;
147        };
148        let Some(jukebox) = block_entity.downcast_ref::<JukeboxBlockEntity>() else {
149            return InteractionResult::Pass;
150        };
151
152        jukebox.pop_out_the_item();
153        InteractionResult::Success
154    }
155
156    fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
157        true
158    }
159
160    fn get_own_signal(
161        &self,
162        _state: BlockStateId,
163        world: &dyn LevelReader,
164        pos: BlockPos,
165        _context: SignalQueryContext,
166    ) -> i32 {
167        let Some(block_entity) = world.get_block_entity(pos) else {
168            return MIN_REDSTONE_SIGNAL;
169        };
170        let Some(jukebox) = block_entity.downcast_ref::<JukeboxBlockEntity>() else {
171            return MIN_REDSTONE_SIGNAL;
172        };
173        if jukebox.is_record_playing() {
174            MAX_REDSTONE_SIGNAL
175        } else {
176            MIN_REDSTONE_SIGNAL
177        }
178    }
179
180    fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
181        true
182    }
183
184    fn get_analog_output_signal(
185        &self,
186        _state: BlockStateId,
187        world: &dyn LevelReader,
188        pos: BlockPos,
189        _direction: Direction,
190    ) -> i32 {
191        let Some(block_entity) = world.get_block_entity(pos) else {
192            return MIN_REDSTONE_SIGNAL;
193        };
194        let Some(jukebox) = block_entity.downcast_ref::<JukeboxBlockEntity>() else {
195            return MIN_REDSTONE_SIGNAL;
196        };
197        jukebox.analog_output_signal()
198    }
199
200    fn new_block_entity(
201        &self,
202        level: Weak<World>,
203        pos: BlockPos,
204        state: BlockStateId,
205    ) -> BlockEntityCreation {
206        BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
207            &vanilla_block_entity_types::JUKEBOX,
208            level,
209            pos,
210            state,
211        ))
212    }
213
214    fn get_block_entity_ticker(
215        &self,
216        _world: &Arc<World>,
217        state: BlockStateId,
218        block_entity_type: BlockEntityTypeRef,
219    ) -> Option<BlockEntityTicker> {
220        if !state.get_value(&BlockStateProperties::HAS_RECORD) {
221            return None;
222        }
223        BlockEntityTicker::for_matching_entity_tick(
224            block_entity_type,
225            &vanilla_block_entity_types::JUKEBOX,
226        )
227    }
228}