Skip to main content

steel_core/behavior/items/
snowball.rs

1//! Snowball item behavior (`SnowballItem`).
2//!
3//! Throwing a snowball spawns a [`SnowballEntity`] from the player's eye, shot
4//! along their look direction, and consumes one snowball unless the player
5//! has infinite materials. Mirrors vanilla `SnowballItem.use`.
6
7use std::sync::Arc;
8
9use steel_macros::item_behavior;
10use steel_protocol::packets::game::SoundSource;
11use steel_registry::stat::vanilla_stat_types;
12use steel_registry::{sound_events, vanilla_entities};
13
14use crate::behavior::context::{InteractionResult, UseItemContext};
15use crate::behavior::item::ItemBehavior;
16use crate::entity::entities::SnowballEntity;
17use crate::entity::{Entity, next_entity_id, spawn_throwable_item_projectile};
18
19/// Vanilla `SnowballItem.PROJECTILE_SHOOT_POWER`.
20const SHOOT_POWER: f32 = 1.5;
21/// Vanilla `SnowballItem.use` throw sound volume.
22const THROW_SOUND_VOLUME: f32 = 0.5;
23/// Vanilla `SnowballItem.use` throw pitch jitter scale: `0.4 / (random * 0.4 + 0.8)`.
24const THROW_PITCH_JITTER_SCALE: f32 = 0.4;
25/// Vanilla `SnowballItem.use` throw pitch jitter base.
26const THROW_PITCH_JITTER_BASE: f32 = 0.8;
27/// Vanilla `SnowballItem.use` throw uncertainty (`spawnProjectileFromRotation`).
28const THROW_UNCERTAINTY: f32 = 1.0;
29
30/// Behavior for the snowball item.
31#[item_behavior(class = "SnowballItem")]
32pub struct SnowballItem;
33
34impl ItemBehavior for SnowballItem {
35    fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
36        let player = context.player;
37        let world = context.world;
38
39        let pitch = THROW_PITCH_JITTER_SCALE
40            / rand::random_range(
41                THROW_PITCH_JITTER_BASE..THROW_PITCH_JITTER_BASE + THROW_PITCH_JITTER_SCALE,
42            );
43        world.play_sound_at(
44            &sound_events::ENTITY_SNOWBALL_THROW,
45            SoundSource::Neutral,
46            player.position(),
47            THROW_SOUND_VOLUME,
48            pitch,
49            None,
50        );
51
52        let mut thrown_item = context.inv.with_item(|item| item.clone());
53        let Some(_snowball) = spawn_throwable_item_projectile(
54            world,
55            player,
56            &mut thrown_item,
57            SHOOT_POWER,
58            THROW_UNCERTAINTY,
59            |spawn_pos| {
60                SnowballEntity::new(
61                    &vanilla_entities::SNOWBALL,
62                    next_entity_id(),
63                    spawn_pos,
64                    Arc::downgrade(world),
65                )
66            },
67        ) else {
68            return InteractionResult::Fail;
69        };
70
71        player.award_stat(&vanilla_stat_types::ITEM_USED, thrown_item.item);
72        let has_infinite_materials = player.has_infinite_materials();
73        context
74            .inv
75            .with_item(|item| item.consume_one(has_infinite_materials));
76
77        InteractionResult::Success
78    }
79}