mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Play the happy ghast's riding sound while the player rides one
LocalPlayer.startRiding starts a RidingEntitySoundInstance for a happy ghast (entity.happy_ghast.riding, volume 5 * clampedLerp(speed, 0, 1)), as it does the two inside sounds for a minecart; Horizon only had the minecart's. Port the instance: it stops when the player gets off or the ghast is silenced, and is silent under water and below 0.01 speed. Part of #3518. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Rx5M6NS763D2WVnb2HmcTH
This commit is contained in:
4 files changed
+132
-3
No files matched your search
@@ -60,6 +60,8 @@ pub enum Ticking {
|
||||
GuardianAttack { guardian: u32 },
|
||||
/// `ElytraOnPlayerSoundInstance`: ticks since it started.
|
||||
Elytra { time: i32 },
|
||||
/// `RidingEntitySoundInstance` for the player riding a happy ghast.
|
||||
RidingHappyGhast { ghast: u32 },
|
||||
}
|
||||
/// What a minecart's sound instances read about it.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -84,6 +86,15 @@ pub struct MobSound {
|
||||
/// `Guardian.getAttackAnimationScale(0)`.
|
||||
pub attack_scale: f32,
|
||||
}
|
||||
/// What a `RidingEntitySoundInstance` reads about the mob the player rides.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RiddenSound {
|
||||
pub id: u32,
|
||||
/// `getDeltaMovement().length()`.
|
||||
pub speed: f64,
|
||||
/// `isSilent`.
|
||||
pub silent: bool,
|
||||
}
|
||||
/// What tickable instances read about the player and the entities they follow.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TickContext {
|
||||
@@ -103,6 +114,8 @@ pub struct TickContext {
|
||||
pub player_speed_sqr: f64,
|
||||
/// `isFallFlying()` of the player.
|
||||
pub player_fall_flying: bool,
|
||||
/// The mob the player rides, when it is not a minecart.
|
||||
pub ridden: Option<RiddenSound>,
|
||||
}
|
||||
/// `AbstractSoundInstance` and its subclasses' fields.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -345,6 +358,26 @@ impl SoundInstance {
|
||||
};
|
||||
s
|
||||
}
|
||||
/// `new RidingEntitySoundInstance(player, happyGhast, false,
|
||||
/// HAPPY_GHAST_RIDING, happyGhast.getSoundSource(), 0, 1, 5)`, from
|
||||
/// `LocalPlayer.startRiding`.
|
||||
pub fn riding_happy_ghast(ghast: &RiddenSound, random: LegacyRandom) -> Self {
|
||||
let mut s = Self::new(
|
||||
"minecraft:entity.happy_ghast.riding",
|
||||
NEUTRAL,
|
||||
0.0,
|
||||
1.0,
|
||||
random,
|
||||
true,
|
||||
0,
|
||||
Attenuation::None,
|
||||
[0.0; 3],
|
||||
false,
|
||||
);
|
||||
s.can_start_silent = true;
|
||||
s.ticking = Ticking::RidingHappyGhast { ghast: ghast.id };
|
||||
s
|
||||
}
|
||||
/// `new BeeFlyingSoundInstance(bee)` (or `BeeAggressiveSoundInstance`):
|
||||
/// silent until the bee moves.
|
||||
pub fn bee(bee: &MobSound, aggressive: bool, random: LegacyRandom) -> Self {
|
||||
@@ -569,6 +602,23 @@ impl SoundInstance {
|
||||
self.volume = 0.0 + 1.0 * scale * scale;
|
||||
self.pitch = 0.7 + 0.5 * scale;
|
||||
}
|
||||
Ticking::RidingHappyGhast { ghast } => {
|
||||
// RidingEntitySoundInstance.tick: canPlaySound, then while the
|
||||
// player still rides it, silent under water (the ghast's sound
|
||||
// is not the underwater one) and else
|
||||
// `volumeAmplifier * Mth.clampedLerp(speed, volumeMin, volumeMax)`.
|
||||
let id = *ghast;
|
||||
let Some(ghast) = ctx.ridden.filter(|r| r.id == id && !r.silent) else {
|
||||
self.stop();
|
||||
return;
|
||||
};
|
||||
let speed = ghast.speed as f32;
|
||||
self.volume = if ctx.player_underwater || speed < 0.01 {
|
||||
0.0
|
||||
} else {
|
||||
5.0 * speed.clamp(0.0, 1.0)
|
||||
};
|
||||
}
|
||||
Ticking::RidingMinecart { cart, underwater } => {
|
||||
let (id, underwater) = (*cart, *underwater);
|
||||
let found = ctx.carts.iter().find(|c| c.id == id);
|
||||
@@ -1328,3 +1378,48 @@ pub mod test_backend {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn happy_ghast_riding_sound_follows_the_ghast_speed() {
|
||||
let ghast = RiddenSound {
|
||||
id: 7,
|
||||
speed: 0.1,
|
||||
silent: false,
|
||||
};
|
||||
let mut s = SoundInstance::riding_happy_ghast(&ghast, SoundEngine::unseeded());
|
||||
assert_eq!(s.volume, 0.0);
|
||||
let mut ctx = TickContext {
|
||||
ridden: Some(ghast),
|
||||
..TickContext::default()
|
||||
};
|
||||
// 5 * clampedLerp(speed, 0, 1).
|
||||
s.tick(&ctx);
|
||||
assert!((s.volume - 0.5).abs() < 1e-6, "{}", s.volume);
|
||||
ctx.ridden = Some(RiddenSound {
|
||||
speed: 3.0,
|
||||
..ghast
|
||||
});
|
||||
s.tick(&ctx);
|
||||
assert_eq!(s.volume, 5.0);
|
||||
ctx.ridden = Some(RiddenSound {
|
||||
speed: 0.005,
|
||||
..ghast
|
||||
});
|
||||
s.tick(&ctx);
|
||||
assert_eq!(s.volume, 0.0);
|
||||
// Under water the ghast's sound is not the underwater one.
|
||||
ctx.ridden = Some(ghast);
|
||||
ctx.player_underwater = true;
|
||||
s.tick(&ctx);
|
||||
assert_eq!(s.volume, 0.0);
|
||||
assert!(!s.stopped);
|
||||
// Off the ghast, it stops.
|
||||
ctx.ridden = None;
|
||||
s.tick(&ctx);
|
||||
assert!(s.stopped);
|
||||
}
|
||||
}
|
||||
@@ -1814,6 +1814,8 @@ struct Game<'a> {
|
||||
cart_sounds: HashSet<u32>,
|
||||
/// The minecart the riding sounds were started for (`LocalPlayer.startRiding`).
|
||||
riding_cart_sounds: Option<u32>,
|
||||
/// The happy ghast its riding sound was started for (`LocalPlayer.startRiding`).
|
||||
riding_ghast_sound: Option<u32>,
|
||||
/// `LocalPlayer.wasFallFlying` for the elytra's flying sound.
|
||||
was_fall_flying: bool,
|
||||
/// Bees whose buzz is playing, and whether it is the aggressive one.
|
||||
@@ -14972,6 +14974,7 @@ impl<'a> Game<'a> {
|
||||
sound_paused: false,
|
||||
cart_sounds: HashSet::new(),
|
||||
riding_cart_sounds: None,
|
||||
riding_ghast_sound: None,
|
||||
was_fall_flying: false,
|
||||
bee_sounds: HashMap::new(),
|
||||
guardian_sounds: HashSet::new(),
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
//! The looping sound instances of mobs: a bee's buzz
|
||||
//! (`ClientPacketListener.postAddEntitySoundInstance`) and a guardian's
|
||||
//! attack hum (entity event 21). The sound engine ticks them with what
|
||||
//! `start_mob_sounds` reads off the mobs.
|
||||
//! attack hum (entity event 21), and the happy ghast's riding sound. The
|
||||
//! sound engine ticks them with what `start_mob_sounds` and
|
||||
//! `start_riding_sound` read off the mobs.
|
||||
use super::Game;
|
||||
use horizon_client::{
|
||||
mobs::{Species, bee, guardian},
|
||||
sound_engine::{MobSound, SoundEngine, SoundInstance, Sounds},
|
||||
sound_engine::{MobSound, RiddenSound, SoundEngine, SoundInstance, Sounds},
|
||||
};
|
||||
|
||||
impl Game<'_> {
|
||||
/// `LocalPlayer.startRiding` on a happy ghast: its riding sound (a
|
||||
/// minecart's are `start_cart_sounds`'). Then what the instance reads
|
||||
/// about the mob the player rides.
|
||||
pub(super) fn start_riding_sound(&mut self) -> Option<RiddenSound> {
|
||||
let ridden = self
|
||||
.riding
|
||||
.and_then(|id| self.mobs.iter().find(|m| m.id == id && !m.removed))
|
||||
.map(|m| {
|
||||
let ghast = m.species() == Species::HappyGhast;
|
||||
let sound = RiddenSound {
|
||||
id: m.id,
|
||||
// Vec3.length.
|
||||
speed: m.velocity.length(),
|
||||
silent: m.silent,
|
||||
};
|
||||
(ghast, sound)
|
||||
});
|
||||
let ghast = ridden.filter(|r| r.0).map(|r| r.1);
|
||||
if let Some(sound) = ghast
|
||||
&& self.riding_ghast_sound != Some(sound.id)
|
||||
&& !sound.silent
|
||||
{
|
||||
let instance = SoundInstance::riding_happy_ghast(&sound, SoundEngine::unseeded());
|
||||
self.audio.play_instance(instance, false);
|
||||
}
|
||||
self.riding_ghast_sound = ghast.map(|g| g.id);
|
||||
ridden.map(|r| r.1)
|
||||
}
|
||||
/// Start the buzz of each bee that has none (or whose anger changed, which
|
||||
/// swaps the flying loop for the aggressive one and back), forget the mobs
|
||||
/// that are gone, and read what the instances follow.
|
||||
|
||||
@@ -35,6 +35,7 @@ impl Game<'_> {
|
||||
}
|
||||
let (vehicle, carts) = self.start_cart_sounds();
|
||||
let mobs = self.start_mob_sounds();
|
||||
let ridden = self.start_riding_sound();
|
||||
let (player_position, player_speed_sqr, player_fall_flying) =
|
||||
self.player.as_ref().map_or(([0.0; 3], 0.0, false), |p| {
|
||||
let at = p.position;
|
||||
@@ -61,6 +62,7 @@ impl Game<'_> {
|
||||
player_position,
|
||||
player_speed_sqr,
|
||||
player_fall_flying,
|
||||
ridden,
|
||||
};
|
||||
self.audio.tick(self.pause, &ctx);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user