mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-08 06:48:22 -10:00
Stop the menu music when a world loads
Minecraft.setLevel -> updateLevelInEngines stops every sound (SoundManager.stop), so the title-screen track ends on joining and the music manager then waits the level's music delay. Horizon only stopped sounds when leaving a world, so music.menu kept playing in it. - A server's client (single player's server thread, LAN, multiplayer) stops every sound when it makes a new level: on Login and on a Respawn into another level. A dimension-changing Respawn also calls MusicManager.stopPlaying, as handleRespawn does. - The in-frame path does the same when the opened world's first player is placed. - Vanilla's Minecraft.player exists from handleLogin on; here it is made at the first teleport, so getSituationalMusic counts a logged-in client as in the world. Otherwise the menu's 0..10-tick replace delay would start the first game track almost at once. - test_music_parity.py scripts the join: a menu-like track, stopall, game music, then a dimension change's stop. Fixes #2746 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
512545ade7
commit
985ece811b
5 files changed
+75
-2
No files matched your search
@@ -4651,6 +4651,7 @@ impl Game<'_> {
|
||||
};
|
||||
let mut player = PlayerMotion::new(self.motion, feet)?;
|
||||
player.yaw = self.yaw;
|
||||
let entering = self.restore_player_state;
|
||||
player.load_saved_state_once(&mut self.restore_player_state, &self.level)?;
|
||||
let spectator = self.mode == horizon_client::command::args::GameMode::Spectator;
|
||||
player.may_fly = self.creative || spectator;
|
||||
@@ -4674,6 +4675,11 @@ impl Game<'_> {
|
||||
self.capture(false);
|
||||
}
|
||||
self.previous_position = feet;
|
||||
if entering {
|
||||
// The world's first player: a server's client gets it with its
|
||||
// level (Minecraft.setLevel), which stops every sound.
|
||||
self.stop_sounds_for_new_level();
|
||||
}
|
||||
self.player = Some(player);
|
||||
self.pad.player_replaced();
|
||||
self.sway = Default::default();
|
||||
|
||||
@@ -43,14 +43,28 @@ impl Game<'_> {
|
||||
};
|
||||
self.audio.tick(self.pause, &ctx);
|
||||
}
|
||||
/// `Minecraft.setLevel` -> `updateLevelInEngines`: `SoundManager.stop()`.
|
||||
/// The music manager keeps its stopped track and, on its next tick, waits
|
||||
/// the level's music delay before the next one.
|
||||
pub(super) fn stop_sounds_for_new_level(&mut self) {
|
||||
self.ambience.clear(&mut self.audio);
|
||||
self.audio.stop_all();
|
||||
}
|
||||
/// `getSituationalMusic` and `getMusicVolume`.
|
||||
fn situational_music(&self) -> (Option<Music>, f32) {
|
||||
pub(super) fn situational_music(&self) -> (Option<Music>, f32) {
|
||||
let empty = BackgroundMusic::default();
|
||||
// The title screen has no player (and no level for the camera probe).
|
||||
// A server's client has its player from handleLogin on, before the
|
||||
// first teleport makes it here: the camera is still at the origin.
|
||||
let eye = if self.title.is_some() {
|
||||
None
|
||||
} else {
|
||||
self.eye_position()
|
||||
self.eye_position().or_else(|| {
|
||||
self.remote
|
||||
.as_ref()
|
||||
.is_some_and(|r| r.logged_in)
|
||||
.then_some([0.0; 3])
|
||||
})
|
||||
};
|
||||
let (background, probe_volume) = match eye {
|
||||
Some(eye) => {
|
||||
|
||||
@@ -115,6 +115,9 @@ pub(super) struct Remote {
|
||||
lang: Arc<Language>,
|
||||
pub(super) registries: Arc<Dynamic>,
|
||||
joined: bool,
|
||||
/// `handleLogin` ran: vanilla's `Minecraft.player` exists from then on,
|
||||
/// while here the player is made at the first teleport.
|
||||
pub(super) logged_in: bool,
|
||||
status: String,
|
||||
pub(super) entity_id: i32,
|
||||
/// The item the player's use began with (`LivingEntity.useItem`).
|
||||
@@ -440,6 +443,7 @@ impl Game<'_> {
|
||||
lang,
|
||||
registries: Arc::new(Dynamic::default()),
|
||||
joined: false,
|
||||
logged_in: false,
|
||||
status: "Connecting to the server...".into(),
|
||||
entity_id: 0,
|
||||
use_item: None,
|
||||
@@ -1018,6 +1022,8 @@ impl Game<'_> {
|
||||
self.set_mode_id(spawn.game_type);
|
||||
self.dim = Dim::from_id(&spawn.dimension);
|
||||
if !same {
|
||||
// A new ClientLevel: Minecraft.setLevel stops every sound.
|
||||
self.stop_sounds_for_new_level();
|
||||
let mut streaming = Streaming::new(
|
||||
Arc::new(NoTerrain),
|
||||
self.pack.context.clone(),
|
||||
@@ -1657,6 +1663,7 @@ impl Game<'_> {
|
||||
r.carried = Some(0);
|
||||
r.online_mode = p.online_mode;
|
||||
r.entity_id = p.player_id;
|
||||
r.logged_in = true;
|
||||
r.view_distance = p.chunk_radius;
|
||||
}
|
||||
self.streaming.radius =
|
||||
@@ -1676,7 +1683,12 @@ impl Game<'_> {
|
||||
// again) and with no item in use.
|
||||
self.potions.effects.clear();
|
||||
self.using = None;
|
||||
let dimension_changed = self.dim != Dim::from_id(&p.spawn.dimension);
|
||||
self.enter_remote_dimension(&p.spawn, true);
|
||||
if dimension_changed {
|
||||
let (music, _) = self.situational_music();
|
||||
self.music.stop_playing(music.as_ref(), &mut self.audio);
|
||||
}
|
||||
self.screen = Screen::Playing;
|
||||
if let Some(r) = self.remote.as_mut() {
|
||||
r.death_message = None;
|
||||
|
||||
@@ -467,6 +467,34 @@ mod tests {
|
||||
assert_eq!(m.current(&engine), Some("minecraft:music.menu"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_level_stops_menu_music_and_waits_for_game_music() {
|
||||
let (mut engine, mut backend) = (engine(), Null::default());
|
||||
let mut m = MusicManager::new(2);
|
||||
let menu = Music::menu();
|
||||
m.start_playing(
|
||||
&menu,
|
||||
&mut Bound {
|
||||
engine: &mut engine,
|
||||
backend: &mut backend,
|
||||
},
|
||||
);
|
||||
// Minecraft.setLevel -> SoundManager.stop(); the next tick has a player.
|
||||
engine.stop_all(&mut backend);
|
||||
let game = Music::game();
|
||||
m.tick(
|
||||
Some(&game),
|
||||
1.0,
|
||||
true,
|
||||
&mut Bound {
|
||||
engine: &mut engine,
|
||||
backend: &mut backend,
|
||||
},
|
||||
);
|
||||
assert!(m.current_instance().is_none());
|
||||
assert!((12000..=24000).contains(&m.next_song_delay()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_music_volume_fades_out_and_stops() {
|
||||
let (mut engine, mut backend) = (engine(), Null::default());
|
||||
|
||||
@@ -95,6 +95,19 @@ def scripted(s):
|
||||
s.tick(5)
|
||||
s.add('stopall')
|
||||
s.tick(5)
|
||||
# Joining a world: the menu-like track plays, Minecraft.setLevel stops every
|
||||
# sound, and the game music (not replacing) waits its own delay; a respawn
|
||||
# into another dimension then stops the music manager's track too.
|
||||
s.add('start', 11, 'DEFAULT')
|
||||
s.situation(end, 20, 600, 1)
|
||||
s.tick(110)
|
||||
s.add('stopall')
|
||||
s.situation(water, 12000, 24000, 0)
|
||||
s.tick(3, loading=1)
|
||||
s.tick(5)
|
||||
s.add('stopall')
|
||||
s.add('stop')
|
||||
s.tick(5)
|
||||
# Events that cannot play.
|
||||
for event in ODD:
|
||||
s.add('start', 7, 'DEFAULT')
|
||||
|
||||
Reference in new issue
Block a user