From 7673d38ed77042a3972d2040524231410e1564c4 Mon Sep 17 00:00:00 2001 From: Kacpherek Date: Mon, 26 Oct 2026 22:41:23 +0100 Subject: [PATCH] Play the amethyst chime when walking on amethyst blocks --- crates/horizon-client/src/game.rs | 8 +++++ crates/horizon-client/src/game/remote.rs | 8 +++++ crates/horizon-client/src/motion.rs | 38 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs index 7596ee871..748e66d44 100644 --- a/crates/horizon-client/src/game.rs +++ b/crates/horizon-client/src/game.rs @@ -4554,6 +4554,14 @@ impl Game<'_> { self.audio .play("minecraft:entity.player.swim", Some(feet), volume, pitch); } + MotionEvent::Chime { volume, pitch } => { + self.audio.play( + "minecraft:block.amethyst_block.chime", + Some(feet), + volume, + pitch, + ); + } MotionEvent::Drown => { // LivingEntity.makeDrownParticles: eight bubbles moving // with the player. diff --git a/crates/horizon-client/src/game/remote.rs b/crates/horizon-client/src/game/remote.rs index 945af4585..ff5d170e6 100644 --- a/crates/horizon-client/src/game/remote.rs +++ b/crates/horizon-client/src/game/remote.rs @@ -3325,6 +3325,14 @@ impl Game<'_> { self.audio .play("minecraft:entity.player.swim", Some(feet), volume, pitch); } + horizon_client::motion::MotionEvent::Chime { volume, pitch } => { + self.audio.play( + "minecraft:block.amethyst_block.chime", + Some(feet), + volume, + pitch, + ); + } // NetherPortalBlock.entityInside on the client's own level: // the portal overlay and its trigger sound (the server moves // the player). diff --git a/crates/horizon-client/src/motion.rs b/crates/horizon-client/src/motion.rs index 234390e23..e654b6c5b 100644 --- a/crates/horizon-client/src/motion.rs +++ b/crates/horizon-client/src/motion.rs @@ -53,6 +53,8 @@ pub struct MotionResources { /// `BlockTags.CAN_GLIDE_THROUGH`: climbable blocks a gliding player passes. pub glide_through: BTreeSet, pub fall_reset: BTreeSet, + /// `BlockTags.CRYSTAL_SOUND_BLOCKS`: blocks that chime under footsteps. + pub crystal_sound: BTreeSet, pub landing: BlockLandingRegistry, pub health: crate::health::HealthResources, pub fluids: FluidResources, @@ -112,6 +114,7 @@ impl MotionResources { climbable: tag("minecraft:climbable")?, glide_through: tag("minecraft:can_glide_through")?, fall_reset: tag("minecraft:fall_damage_resetting")?, + crystal_sound: tag("minecraft:crystal_sound_blocks")?, }) } } @@ -145,6 +148,11 @@ pub enum MotionEvent { state: BlockStateId, in_water: bool, }, + /// `Entity.playAmethystStepSound`: `block.amethyst_block.chime`. + Chime { + volume: f32, + pitch: f32, + }, /// `Entity.waterSwimSound`: `entity.player.swim`. Swim { volume: f32, @@ -279,6 +287,10 @@ pub struct PlayerMotion { /// `Entity.moveDist`, `flyDist` and `nextStep`: footsteps come once per /// metre walked, not once per move. pub steps: MovementDistances, + /// `Entity.crystalSoundIntensity` and `lastCrystalSoundPlayTick`: the + /// amethyst chime grows with steps taken close together. + pub crystal_sound_intensity: f32, + pub last_crystal_sound_play_tick: i32, pub needs_sync: bool, pub sync_position: bool, /// Moved by an ender pearl (`ServerPlayer.teleport`): a server sends the @@ -470,6 +482,8 @@ impl PlayerMotion { next_step: 1.0, ..MovementDistances::default() }, + crystal_sound_intensity: 0.0, + last_crystal_sound_play_tick: 0, needs_sync: false, sync_position: false, teleported: false, @@ -2065,6 +2079,30 @@ impl MovementEmissionWorld for MotionWorld<'_, '_, '_> { state, in_water, }); + // Entity.walkingStepSound: the amethyst chime after the step. + let name = &self + .assets + .registry + .state(state) + .ok_or("missing scene state")? + .name; + let tick = self.player.health.tick_count; + if self.resources.crystal_sound.contains(name) + && tick >= self.player.last_crystal_sound_play_tick + 20 + { + // Entity.playAmethystStepSound (the level random stands in for + // the entity's). + let elapsed = f64::from(tick - self.player.last_crystal_sound_play_tick); + let decayed = self.player.crystal_sound_intensity * 0.997_f64.powf(elapsed) as f32; + let intensity = 1.0_f32.min(decayed + 0.07); + self.player.crystal_sound_intensity = intensity; + let pitch = 0.5 + intensity * self.scene.random.next_float() * 1.2; + let volume = 0.1 + intensity * 1.2; + self.player + .events + .push(MotionEvent::Chime { volume, pitch }); + self.player.last_crystal_sound_play_tick = tick; + } Ok(()) } fn step_event(&mut self, _: BlockStateId) -> Result<()> {