steel_core/behavior/items/
dye.rs1use steel_macros::item_behavior;
4use steel_protocol::packets::game::SoundSource;
5use steel_registry::data_components::vanilla_components::DYE;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::sound_events::ITEM_DYE_USE;
8use steel_utils::Downcast as _;
9use steel_utils::types::{InteractionHand, SignTextSlot};
10
11use crate::behavior::{InteractionResult, ItemBehavior, SignApplicator};
12use crate::block_entity::{BlockEntity as _, entities::SignBlockEntity};
13use crate::entity::entities::SheepEntity;
14use crate::entity::{Entity, LivingEntity};
15use crate::player::Player;
16use crate::world::World;
17
18#[item_behavior(class = "DyeItem")]
24pub struct DyeItem;
25
26impl ItemBehavior for DyeItem {
27 fn as_sign_applicator(&self) -> Option<&dyn SignApplicator> {
28 Some(self)
29 }
30
31 fn interact_living_entity(
32 &self,
33 stack: &mut ItemStack,
34 _player: &Player,
35 target: &dyn LivingEntity,
36 _hand: InteractionHand,
37 ) -> InteractionResult {
38 let Some(dye_color) = stack.get(DYE).copied() else {
39 return InteractionResult::Pass;
40 };
41 let Some(sheep) = target.downcast_ref::<SheepEntity>() else {
42 return InteractionResult::Pass;
43 };
44
45 if !Entity::is_alive(sheep) || sheep.is_sheared() || sheep.color() == dye_color {
46 return InteractionResult::Pass;
47 }
48
49 sheep.play_sound(&ITEM_DYE_USE, 1.0, 1.0);
50 sheep.set_color(dye_color);
51 stack.shrink_one();
52
53 InteractionResult::Success
54 }
55}
56
57impl SignApplicator for DyeItem {
58 fn try_apply_to_sign(
59 &self,
60 world: &World,
61 sign: &SignBlockEntity,
62 slot: SignTextSlot,
63 item: &ItemStack,
64 _player: &Player,
65 ) -> bool {
66 let Some(dye_color) = item.get(DYE).copied() else {
67 return false;
68 };
69
70 if !sign.set_color(slot, dye_color) {
71 return false;
72 }
73
74 world.play_sound(
75 &ITEM_DYE_USE,
76 SoundSource::Blocks,
77 sign.get_block_pos(),
78 1.0,
79 1.0,
80 None,
81 );
82 true
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use std::sync::Arc;
89
90 use steel_registry::{DyeColor, init_vanilla_registry, vanilla_blocks, vanilla_items};
91 use steel_utils::BlockPos;
92 use text_components::TextComponent;
93
94 use super::*;
95 use crate::block_entity::entities::SignText;
96 use crate::test_support::{TestPlayerBuilder, fresh_test_world};
97
98 #[test]
99 fn dye_sign_applicator_updates_color_and_respects_existing_color() {
100 init_vanilla_registry();
101 let world_fixture = fresh_test_world("dye_sign_applicator");
102 let world = &world_fixture.world;
103 let sign = SignBlockEntity::new(
104 Arc::downgrade(world),
105 BlockPos::new(0, 64, 0),
106 vanilla_blocks::OAK_SIGN.default_state(),
107 );
108
109 let mut dye_stack = ItemStack::new(&vanilla_items::RED_DYE);
110 dye_stack.set(DYE, DyeColor::Red);
111
112 let player = TestPlayerBuilder::new(Arc::clone(world), "SignDyer", 1).build();
113 let applicator = DyeItem;
114
115 assert!(applicator.try_apply_to_sign(
116 world,
117 &sign,
118 SignTextSlot::Front,
119 &dye_stack,
120 &player
121 ));
122 assert_eq!(sign.get_text(SignTextSlot::Front).color, DyeColor::Red);
123 assert!(!applicator.try_apply_to_sign(
124 world,
125 &sign,
126 SignTextSlot::Front,
127 &dye_stack,
128 &player
129 ));
130
131 let mut empty_text = SignText::new();
132 assert!(!applicator.can_apply_to_sign(&empty_text, &dye_stack, &player));
133
134 empty_text.set_message(0, TextComponent::plain("Hello"));
135 assert!(applicator.can_apply_to_sign(&empty_text, &dye_stack, &player));
136 }
137}