steel_core/behavior/blocks/decoration/
jukebox_block.rs1use 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#[block_behavior]
29pub struct JukeboxBlock {
30 block: BlockRef,
31}
32
33impl JukeboxBlock {
34 #[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 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}