Skip to main content

steel_core/behavior/items/
ink_sac.rs

1//! Ink sac and glow ink sac item behaviors.
2//!
3//! Ports vanilla `InkSacItem` and `GlowInkSacItem` (`SignApplicator`), along with
4//! the guards `SignBlock.useItemOn` applies before delegating to them.
5//!
6//! Vanilla dispatches these from the block side via the `SignApplicator` interface;
7//! Steel implements them as item behaviors to match the existing `HoneycombItem`
8//! sign-waxing path.
9
10use steel_macros::item_behavior;
11use steel_protocol::packets::game::SoundSource;
12use steel_registry::{
13    sound_event::SoundEventRef,
14    sound_events::{ITEM_GLOW_INK_SAC_USE, ITEM_INK_SAC_USE},
15    stat::vanilla_stat_types,
16    vanilla_game_events,
17};
18use steel_utils::Downcast as _;
19
20use crate::{
21    behavior::{InteractionResult, ItemBehavior, UseOnContext, blocks::is_facing_front_text},
22    block_entity::{BlockEntity, entities::SignBlockEntity},
23    world::game_event::GameEventContext,
24};
25
26/// Shared applicator path for both ink sac variants.
27fn apply_glow(context: &mut UseOnContext, glowing: bool) -> InteractionResult {
28    let pos = context.hit_result.block_pos;
29
30    let Some(block_entity) = context.world.get_block_entity(pos) else {
31        return InteractionResult::Pass;
32    };
33    let Some(sign) = block_entity.downcast_ref::<SignBlockEntity>() else {
34        return InteractionResult::Pass;
35    };
36
37    if !context.player.abilities.lock().may_build
38        || sign.is_waxed()
39        || sign.is_other_player_editing(context.player.gameprofile.id)
40    {
41        return InteractionResult::TryEmptyHandInteraction;
42    }
43
44    let state = context.world.get_block_state(pos);
45    let is_front_text = is_facing_front_text(state, pos, context.player);
46
47    // Vanilla `SignApplicator.canApplyToSign` refuses a blank sign.
48    if !sign.get_text(is_front_text).has_message() {
49        return InteractionResult::TryEmptyHandInteraction;
50    }
51
52    if !sign.set_glowing(is_front_text, glowing) {
53        return InteractionResult::TryEmptyHandInteraction;
54    }
55
56    sign.set_changed();
57    if let Some(nbt) = sign.get_update_tag() {
58        context
59            .world
60            .broadcast_block_entity_update(pos, sign.get_type(), nbt);
61    }
62
63    context.world.game_event(
64        &vanilla_game_events::BLOCK_CHANGE,
65        pos,
66        &GameEventContext::new(Some(context.player), Some(state)),
67    );
68
69    let has_infinite_materials = context.player.has_infinite_materials();
70    let item_used = context.inv.with_item(|item| {
71        item.consume_one(has_infinite_materials);
72        item.item
73    });
74    context
75        .player
76        .award_stat(&vanilla_stat_types::ITEM_USED, item_used);
77
78    context
79        .world
80        .play_sound(sound_for(glowing), SoundSource::Blocks, pos, 1.0, 1.0, None);
81
82    InteractionResult::Success
83}
84
85const fn sound_for(glowing: bool) -> SoundEventRef {
86    if glowing {
87        &ITEM_GLOW_INK_SAC_USE
88    } else {
89        &ITEM_INK_SAC_USE
90    }
91}
92
93/// Behavior for the ink sac item. Removes glow from sign text.
94#[item_behavior]
95pub struct InkSacItem;
96
97impl ItemBehavior for InkSacItem {
98    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
99        apply_glow(context, false)
100    }
101}
102
103/// Behavior for the glow ink sac item. Makes sign text glow.
104#[item_behavior]
105pub struct GlowInkSacItem;
106
107impl ItemBehavior for GlowInkSacItem {
108    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
109        apply_glow(context, true)
110    }
111}