Skip to main content

steel_core/behavior/items/
throw_projectile.rs

1//! Shared item use logic for throwing projectiles
2
3use glam::DVec3;
4use steel_protocol::packets::game::SoundSource;
5use steel_registry::sound_event::SoundEventRef;
6use steel_registry::stat::vanilla_stat_types;
7
8use crate::behavior::context::UseItemContext;
9use crate::entity::{
10    Entity, Projectile, SharedEntity, ThrowableItemProjectile, spawn_throwable_item_projectile,
11};
12
13const THROW_PITCH_NUMERATOR: f32 = 0.4;
14const THROW_PITCH_DIVISOR_MIN: f32 = 0.8;
15const THROW_PITCH_DIVISOR_MAX: f32 = 1.2;
16
17/// The per-item constants of vanilla's throw `use`.
18pub(super) struct ThrowParams {
19    /// Sound played before the projectile spawns.
20    pub sound: SoundEventRef,
21    /// Category that sound is played under.
22    pub sound_source: SoundSource,
23    /// Volume that sound is played at.
24    pub sound_volume: f32,
25    /// `spawnProjectileFromRotation`'s `yOffset`, in degrees.
26    pub y_offset: f32,
27    /// The item's `PROJECTILE_SHOOT_POWER`.
28    pub power: f32,
29    /// `spawnProjectileFromRotation` uncertainty.
30    pub uncertainty: f32,
31}
32
33/// Throws a projectile plays its sound awards `ITEM_USED` and consumes one item
34pub(super) fn throw_item_projectile<E>(
35    context: &mut UseItemContext,
36    params: &ThrowParams,
37    create: impl FnOnce(DVec3) -> E,
38) -> Option<SharedEntity>
39where
40    E: Projectile + ThrowableItemProjectile + Entity + 'static,
41{
42    let player = context.player;
43    let world = context.world;
44
45    let pitch = THROW_PITCH_NUMERATOR
46        / rand::random_range(THROW_PITCH_DIVISOR_MIN..THROW_PITCH_DIVISOR_MAX);
47    world.play_sound_at(
48        params.sound,
49        params.sound_source,
50        player.position(),
51        params.sound_volume,
52        pitch,
53        None,
54    );
55
56    let mut thrown_item = context.inv.with_item(|item| item.clone());
57    let projectile = spawn_throwable_item_projectile(
58        world,
59        player,
60        &mut thrown_item,
61        params.y_offset,
62        params.power,
63        params.uncertainty,
64        create,
65    );
66    if projectile.is_none() {
67        log::debug!("world rejected a thrown projectile; still consuming the stack");
68    }
69
70    player.award_stat(&vanilla_stat_types::ITEM_USED, thrown_item.item);
71    let has_infinite_materials = player.has_infinite_materials();
72    context
73        .inv
74        .with_item(|item| item.consume_one(has_infinite_materials));
75
76    projectile
77}