Add combat music transitions based on number of enemies in player radius.

This commit is contained in:
Avi Weinstock 2021-04-06 21:54:01 -04:00
parent 54024ce401
commit 5bfdd5f1af
10 changed files with 355 additions and 148 deletions

View File

@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- "Poise" renamed to "Stun resilience" - "Poise" renamed to "Stun resilience"
- Stun resilience stat display - Stun resilience stat display
- Villagers and guards now spawn with potions, and know how to use them. - Villagers and guards now spawn with potions, and know how to use them.
- Combat music in dungeons when within range of enemies.
### Changed ### Changed

View File

@ -6,17 +6,17 @@
( (
tracks: [ tracks: [
( Individual((
title: "Dank Dungeon", title: "Dank Dungeon",
path: "voxygen.audio.soundtrack.dungeon.dank_dungeon", path: "voxygen.audio.soundtrack.dungeon.dank_dungeon",
length: 130.0, length: 130.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Calming Hills", title: "Calming Hills",
path: "voxygen.audio.soundtrack.overworld.calming_hills", path: "voxygen.audio.soundtrack.overworld.calming_hills",
length: 101.0, length: 101.0,
@ -25,10 +25,10 @@
(Mountain, 1) (Mountain, 1)
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Ultimafounding; mixed by Robotnik", artist: "Ultimafounding; mixed by Robotnik",
), )),
( Individual((
title: "Fiesta Del Pueblo", title: "Fiesta Del Pueblo",
path: "voxygen.audio.soundtrack.overworld.fiesta_del_pueblo", path: "voxygen.audio.soundtrack.overworld.fiesta_del_pueblo",
length: 183.0, length: 183.0,
@ -37,50 +37,50 @@
(Desert, 1) (Desert, 1)
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic; mixed by Robotnik", artist: "Aeronic; mixed by Robotnik",
), )),
( Individual((
title: "Ruination", title: "Ruination",
path: "voxygen.audio.soundtrack.dungeon.ruination", path: "voxygen.audio.soundtrack.dungeon.ruination",
length: 135.0, length: 135.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Dank Hallows", title: "Dank Hallows",
path: "voxygen.audio.soundtrack.cave.dank_hallows", path: "voxygen.audio.soundtrack.cave.dank_hallows",
length: 227.0, length: 227.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Cave), site: Some(Cave),
activity: Explore, activity: State(Explore),
artist: "Flashbang", artist: "Flashbang",
), )),
( Individual((
title: "Vast Onslaught", title: "Vast Onslaught",
path: "voxygen.audio.soundtrack.dungeon.vast_onslaught", path: "voxygen.audio.soundtrack.dungeon.vast_onslaught",
length: 237.0, length: 237.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Sacred Temple", title: "Sacred Temple",
path: "voxygen.audio.soundtrack.dungeon.sacred_temple", path: "voxygen.audio.soundtrack.dungeon.sacred_temple",
length: 75.0, length: 75.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "True Nature", title: "True Nature",
path: "voxygen.audio.soundtrack.overworld.true_nature", path: "voxygen.audio.soundtrack.overworld.true_nature",
length: 169.0, length: 169.0,
@ -89,10 +89,10 @@
(Forest, 1), (Forest, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "DaforLynx", artist: "DaforLynx",
), )),
( Individual((
title: "Jungle Ambient", title: "Jungle Ambient",
path: "voxygen.audio.soundtrack.overworld.jungle_ambient", path: "voxygen.audio.soundtrack.overworld.jungle_ambient",
length: 218.0, length: 218.0,
@ -101,10 +101,10 @@
(Jungle, 1), (Jungle, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Ethereal Bonds", title: "Ethereal Bonds",
path: "voxygen.audio.soundtrack.overworld.ethereal_bonds", path: "voxygen.audio.soundtrack.overworld.ethereal_bonds",
length: 59.0, length: 59.0,
@ -113,10 +113,10 @@
(Mountain, 1), (Mountain, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Leap of Faith", title: "Leap of Faith",
path: "voxygen.audio.soundtrack.overworld.leap_of_faith", path: "voxygen.audio.soundtrack.overworld.leap_of_faith",
length: 269.0, length: 269.0,
@ -126,10 +126,10 @@
(Lake, 1), (Lake, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Highland of the Hawk", title: "Highland of the Hawk",
path: "voxygen.audio.soundtrack.overworld.highland_of_the_hawk", path: "voxygen.audio.soundtrack.overworld.highland_of_the_hawk",
length: 283.0, length: 283.0,
@ -139,10 +139,10 @@
(Mountain, 1), (Mountain, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Verdant Glades", title: "Verdant Glades",
path: "voxygen.audio.soundtrack.overworld.verdant_glades", path: "voxygen.audio.soundtrack.overworld.verdant_glades",
length: 97.0, length: 97.0,
@ -151,10 +151,10 @@
(Grassland, 1), (Grassland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Calling Wild", title: "Calling Wild",
path: "voxygen.audio.soundtrack.overworld.calling_wild", path: "voxygen.audio.soundtrack.overworld.calling_wild",
length: 160.0, length: 160.0,
@ -163,10 +163,10 @@
(Grassland, 1), (Grassland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Ultimafounding", artist: "Ultimafounding",
), )),
( Individual((
title: "Drifting Along", title: "Drifting Along",
path: "voxygen.audio.soundtrack.overworld.drifting_along", path: "voxygen.audio.soundtrack.overworld.drifting_along",
length: 164.0, length: 164.0,
@ -176,35 +176,35 @@
(Ocean, 1), (Ocean, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "DaforLynx", artist: "DaforLynx",
), )),
( Individual((
title: "Winter Falls", title: "Winter Falls",
path: "voxygen.audio.soundtrack.overworld.winter_falls", path: "voxygen.audio.soundtrack.overworld.winter_falls",
length: 215.0, length: 215.0,
timing: Some(Day), timing: Some(Day),
biomes: [ biomes: [
(Snowland, 1), (Snowland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "DaforLynx", artist: "DaforLynx",
), )),
( Individual((
title: "Short Meandering", title: "Short Meandering",
path: "voxygen.audio.soundtrack.overworld.short_meandering", path: "voxygen.audio.soundtrack.overworld.short_meandering",
length: 147.0, length: 147.0,
timing: Some(Night), timing: Some(Night),
biomes: [ biomes: [
(Desert, 1), (Desert, 1),
(Mountain, 1), (Mountain, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Ap1evideogame", artist: "Ap1evideogame",
), )),
( Individual((
title: "Oceania", title: "Oceania",
path: "voxygen.audio.soundtrack.overworld.oceania", path: "voxygen.audio.soundtrack.overworld.oceania",
length: 135.0, length: 135.0,
@ -214,10 +214,10 @@
(Ocean, 1), (Ocean, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Eden", artist: "Eden",
), )),
( Individual((
title: "A Solemn Quest", title: "A Solemn Quest",
path: "voxygen.audio.soundtrack.overworld.a_solemn_quest", path: "voxygen.audio.soundtrack.overworld.a_solemn_quest",
length: 206.0, length: 206.0,
@ -227,10 +227,10 @@
(Mountain, 1), (Mountain, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Eden", artist: "Eden",
), )),
( Individual((
title: "Into The Dark Forest", title: "Into The Dark Forest",
path: "voxygen.audio.soundtrack.overworld.into_the_dark_forest", path: "voxygen.audio.soundtrack.overworld.into_the_dark_forest",
length: 184.0, length: 184.0,
@ -240,23 +240,23 @@
(Jungle, 1), (Jungle, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Field Grazing", title: "Field Grazing",
path: "voxygen.audio.soundtrack.overworld.field_grazing", path: "voxygen.audio.soundtrack.overworld.field_grazing",
length: 154.0, length: 154.0,
timing: Some(Day), timing: Some(Day),
biomes: [ biomes: [
(Grassland, 1), (Grassland, 1),
(Forest, 1), (Forest, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Wandering Voices", title: "Wandering Voices",
path: "voxygen.audio.soundtrack.overworld.wandering_voices", path: "voxygen.audio.soundtrack.overworld.wandering_voices",
length: 137.0, length: 137.0,
@ -265,10 +265,10 @@
(Grassland, 1), (Grassland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Snowtop Volume", title: "Snowtop Volume",
path: "voxygen.audio.soundtrack.overworld.snowtop_volume", path: "voxygen.audio.soundtrack.overworld.snowtop_volume",
length: 89.0, length: 89.0,
@ -277,20 +277,20 @@
(Snowland, 1), (Snowland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Mineral Deposits", title: "Mineral Deposits",
path: "voxygen.audio.soundtrack.cave.mineral_deposits", path: "voxygen.audio.soundtrack.cave.mineral_deposits",
length: 148.0, length: 148.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Cave), site: Some(Cave),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Moonbeams", title: "Moonbeams",
path: "voxygen.audio.soundtrack.overworld.moonbeams", path: "voxygen.audio.soundtrack.overworld.moonbeams",
length: 158.0, length: 158.0,
@ -299,23 +299,23 @@
(Snowland, 1), (Snowland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Serene Meadows", title: "Serene Meadows",
path: "voxygen.audio.soundtrack.overworld.serene_meadows", path: "voxygen.audio.soundtrack.overworld.serene_meadows",
length: 173.0, length: 173.0,
timing: Some(Night), timing: Some(Night),
biomes: [ biomes: [
(Grassland, 1), (Grassland, 1),
(Desert, 1), (Desert, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "Aeronic", artist: "Aeronic",
), )),
( Individual((
title: "Just The Beginning", title: "Just The Beginning",
path: "voxygen.audio.soundtrack.overworld.just_the_beginning", path: "voxygen.audio.soundtrack.overworld.just_the_beginning",
length: 188.0, length: 188.0,
@ -324,10 +324,10 @@
(Grassland, 1), (Grassland, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Campfire Stories", title: "Campfire Stories",
path: "voxygen.audio.soundtrack.overworld.campfire_stories", path: "voxygen.audio.soundtrack.overworld.campfire_stories",
length: 100.0, length: 100.0,
@ -336,10 +336,10 @@
(Forest, 1), (Forest, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Limits", title: "Limits",
path: "voxygen.audio.soundtrack.overworld.limits", path: "voxygen.audio.soundtrack.overworld.limits",
length: 203.0, length: 203.0,
@ -348,20 +348,20 @@
(Mountain, 1), (Mountain, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Down The Rabbit Hole", title: "Down The Rabbit Hole",
path: "voxygen.audio.soundtrack.dungeon.down_the_rabbit_hole", path: "voxygen.audio.soundtrack.dungeon.down_the_rabbit_hole",
length: 244.0, length: 244.0,
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
), )),
( Individual((
title: "Between The Fairies", title: "Between The Fairies",
path: "voxygen.audio.soundtrack.overworld.between_the_fairies", path: "voxygen.audio.soundtrack.overworld.between_the_fairies",
length: 175.0, length: 175.0,
@ -370,8 +370,26 @@
(Forest, 1), (Forest, 1),
], ],
site: Some(Void), site: Some(Void),
activity: Explore, activity: State(Explore),
artist: "badbbad", artist: "badbbad",
) )),
Segmented(
title: "Combat1",
author: "DaforLynx",
timing: None,
biomes: [],
site: Some(Dungeon),
segments: [
("voxygen.audio.soundtrack.combat1.combat1-hi-loop", 54.0, State(Combat(High))),
("voxygen.audio.soundtrack.combat1.combat1-hi-start", 55.0, Transition(Explore, Combat(High))),
("voxygen.audio.soundtrack.combat1.combat1-lo-loop", 7.0, State(Combat(Low))),
("voxygen.audio.soundtrack.combat1.combat1-lo-start", 10.0, Transition(Explore, Combat(Low))),
("voxygen.audio.soundtrack.combat1.combat1-trans-hi-lo", 10.0, Transition(Combat(High), Combat(Low))),
("voxygen.audio.soundtrack.combat1.combat1-trans-lo-hi", 7.0, Transition(Combat(Low), Combat(High))),
// temporary until more assets exist:
("voxygen.audio.soundtrack.combat1.combat1-trans-hi-lo", 10.0, Transition(Combat(High), Explore)),
("voxygen.audio.soundtrack.combat1.combat1-lo-loop", 7.0, Transition(Combat(Low), Explore)),
],
),
] ]
) )

BIN
assets/voxygen/audio/soundtrack/combat1/combat1-hi-loop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/combat1/combat1-hi-start.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/combat1/combat1-lo-loop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/combat1/combat1-lo-start.ogg (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -41,6 +41,7 @@ enum ChannelState {
pub enum MusicChannelTag { pub enum MusicChannelTag {
TitleMusic, TitleMusic,
Exploration, Exploration,
Combat,
} }
/// A MusicChannel uses a non-positional audio sink designed to play music which /// A MusicChannel uses a non-positional audio sink designed to play music which

View File

@ -53,17 +53,21 @@ use common_sys::state::State;
use rand::{prelude::SliceRandom, thread_rng, Rng}; use rand::{prelude::SliceRandom, thread_rng, Rng};
use serde::Deserialize; use serde::Deserialize;
use std::time::Instant; use std::time::Instant;
use tracing::warn; use tracing::{debug, trace, warn};
/// Collection of all the tracks /// Collection of all the tracks
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Deserialize)]
struct SoundtrackCollection { struct SoundtrackCollection<T> {
/// List of tracks /// List of tracks
tracks: Vec<SoundtrackItem>, tracks: Vec<T>,
}
impl<T> Default for SoundtrackCollection<T> {
fn default() -> Self { Self { tracks: Vec::new() } }
} }
/// Configuration for a single music track in the soundtrack /// Configuration for a single music track in the soundtrack
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct SoundtrackItem { pub struct SoundtrackItem {
/// Song title /// Song title
title: String, title: String,
@ -82,14 +86,38 @@ pub struct SoundtrackItem {
activity: MusicActivity, activity: MusicActivity,
} }
#[derive(Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize)]
enum MusicActivity { enum RawSoundtrackItem {
Individual(SoundtrackItem),
Segmented {
title: String,
timing: Option<DayPeriod>,
biomes: Vec<(BiomeKind, u8)>,
site: Option<SitesKind>,
segments: Vec<(String, f32, MusicActivity)>,
},
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
enum CombatIntensity {
Low,
High,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
enum MusicActivityState {
Explore, Explore,
Combat, Combat(CombatIntensity),
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
enum MusicActivity {
State(MusicActivityState),
Transition(MusicActivityState, MusicActivityState),
} }
/// Allows control over when a track should play based on in-game time of day /// Allows control over when a track should play based on in-game time of day
#[derive(Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize, PartialEq)]
enum DayPeriod { enum DayPeriod {
/// 8:00 AM to 7:30 PM /// 8:00 AM to 7:30 PM
Day, Day,
@ -109,7 +137,7 @@ enum PlayState {
/// Provides methods to control music playback /// Provides methods to control music playback
pub struct MusicMgr { pub struct MusicMgr {
/// Collection of all the tracks /// Collection of all the tracks
soundtrack: AssetHandle<SoundtrackCollection>, soundtrack: AssetHandle<SoundtrackCollection<SoundtrackItem>>,
/// Instant at which the current track began playing /// Instant at which the current track began playing
began_playing: Instant, began_playing: Instant,
/// Time until the next track should be played /// Time until the next track should be played
@ -117,6 +145,8 @@ pub struct MusicMgr {
/// The title of the last track played. Used to prevent a track /// The title of the last track played. Used to prevent a track
/// being played twice in a row /// being played twice in a row
last_track: String, last_track: String,
/// The previous track's activity kind, for transitions
last_activity: MusicActivityState,
} }
impl Default for MusicMgr { impl Default for MusicMgr {
@ -126,6 +156,7 @@ impl Default for MusicMgr {
began_playing: Instant::now(), began_playing: Instant::now(),
next_track_change: 0.0, next_track_change: 0.0,
last_track: String::from("None"), last_track: String::from("None"),
last_activity: MusicActivityState::Explore,
} }
} }
} }
@ -144,41 +175,97 @@ impl MusicMgr {
// player_alt = position.0.z; // player_alt = position.0.z;
//} //}
use common::comp::{Health, Pos};
use specs::{Join, WorldExt};
use MusicActivityState::*;
let mut activity_state = Explore;
let player = client.entity();
let ecs = state.ecs();
let entities = ecs.entities();
let positions = ecs.read_component::<Pos>();
let healths = ecs.read_component::<Health>();
if let Some(player_pos) = positions.get(player) {
const NEARBY_RADIUS: f32 = 50.0;
// TODO: consider filtering by Alignment::Enemy (requires sync changes)
let num_nearby_entities: u32 = (&entities, &positions, &healths)
.join()
.map(|(entity, pos, _)| {
if entity != player
&& (player_pos.0 - pos.0).magnitude_squared() < NEARBY_RADIUS.powf(2.0)
{
1
} else {
0
}
})
.sum();
if num_nearby_entities > 2 {
activity_state = Combat(CombatIntensity::High);
} else if num_nearby_entities >= 1 {
activity_state = Combat(CombatIntensity::Low);
}
}
let activity = if self.last_activity != activity_state {
MusicActivity::Transition(self.last_activity, activity_state)
} else {
MusicActivity::State(activity_state)
};
let interrupt = matches!(activity, MusicActivity::Transition(_, _));
if audio.music_enabled() if audio.music_enabled()
&& !self.soundtrack.read().tracks.is_empty() && !self.soundtrack.read().tracks.is_empty()
&& self.began_playing.elapsed().as_secs_f32() > self.next_track_change && (self.began_playing.elapsed().as_secs_f32() > self.next_track_change || interrupt)
{ {
self.play_random_track(audio, state, client); trace!("in audio maintain: {:?} {:?}", self.last_activity, activity);
if let Ok(()) = self.play_random_track(audio, state, client, &activity) {
self.last_activity = activity_state;
}
} }
} }
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) { fn play_random_track(
&mut self,
audio: &mut AudioFrontend,
state: &State,
client: &Client,
activity: &MusicActivity,
) -> Result<(), ()> {
let mut rng = thread_rng(); let mut rng = thread_rng();
// Adds a bit of randomness between plays // Adds a bit of randomness between plays
let silence_between_tracks_seconds: f32 = rng.gen_range(60.0..120.0); let silence_between_tracks_seconds: f32 =
if matches!(activity, MusicActivity::State(MusicActivityState::Explore)) {
rng.gen_range(60.0..120.0)
} else {
0.0
};
let is_dark = (state.get_day_period().is_dark()) as bool; let is_dark = (state.get_day_period().is_dark()) as bool;
let current_period_of_day = Self::get_current_day_period(is_dark); let current_period_of_day = Self::get_current_day_period(is_dark);
let current_biome = client.current_biome(); let current_biome = client.current_biome();
let current_site = client.current_site(); let current_site = client.current_site();
// Filters out tracks not matching the timing, site, and biome // Filter the soundtrack in stages, so that we don't overprune it if there are
// too many constraints. Returning Err(()) signals that we couldn't find
// an appropriate track for the current state, and hence the state
// machine for the activity shouldn't be updated.
let mut res = Ok(());
let soundtrack = self.soundtrack.read(); let soundtrack = self.soundtrack.read();
let maybe_tracks = soundtrack // First, filter out tracks not matching the timing, site, and biome
let mut maybe_tracks = soundtrack
.tracks .tracks
.iter() .iter()
.filter(|track| track.activity == MusicActivity::Explore)
.filter(|track| { .filter(|track| {
!track.title.eq(&self.last_track) (match &track.timing {
&& match &track.timing { Some(period_of_day) => period_of_day == &current_period_of_day,
Some(period_of_day) => period_of_day == &current_period_of_day, None => true,
None => true, }) && match &track.site {
} Some(site) => site == &current_site,
&& match &track.site { None => true,
Some(site) => site == &current_site, }
None => true,
}
}) })
.filter(|track| { .filter(|track| {
let mut result = false; let mut result = false;
@ -194,6 +281,37 @@ impl MusicMgr {
result result
}) })
.collect::<Vec<&SoundtrackItem>>(); .collect::<Vec<&SoundtrackItem>>();
// Second, filter out any tracks that don't match the current activity.
let filtered_tracks: Vec<_> = maybe_tracks
.iter()
.filter(|track| &track.activity == activity)
.cloned()
.collect();
trace!(
"maybe_len: {}, filtered len: {}",
maybe_tracks.len(),
filtered_tracks.len()
);
if !filtered_tracks.is_empty() {
maybe_tracks = filtered_tracks;
} else {
res = Err(());
}
// Third, prevent playing the last track if possible (though don't return Err
// here, since the combat music is intended to loop)
let filtered_tracks: Vec<_> = maybe_tracks
.iter()
.filter(|track| !track.title.eq(&self.last_track))
.cloned()
.collect();
trace!(
"maybe_len: {}, filtered len: {}",
maybe_tracks.len(),
filtered_tracks.len()
);
if !filtered_tracks.is_empty() {
maybe_tracks = filtered_tracks;
}
// Randomly selects a track from the remaining tracks weighted based // Randomly selects a track from the remaining tracks weighted based
// on the biome // on the biome
@ -213,6 +331,10 @@ impl MusicMgr {
} }
chance chance
}); });
debug!(
"selecting new track for {:?}: {:?}",
activity, new_maybe_track
);
if let Ok(track) = new_maybe_track { if let Ok(track) = new_maybe_track {
//println!("Now playing {:?}", track.title); //println!("Now playing {:?}", track.title);
@ -220,7 +342,19 @@ impl MusicMgr {
self.began_playing = Instant::now(); self.began_playing = Instant::now();
self.next_track_change = track.length + silence_between_tracks_seconds; self.next_track_change = track.length + silence_between_tracks_seconds;
audio.play_music(&track.path, MusicChannelTag::Exploration); let tag = if matches!(
activity,
MusicActivity::State(MusicActivityState::Explore)
| MusicActivity::Transition(_, MusicActivityState::Explore)
) {
MusicChannelTag::Exploration
} else {
MusicChannelTag::Combat
};
audio.play_music(&track.path, tag);
res
} else {
Err(())
} }
} }
@ -232,23 +366,58 @@ impl MusicMgr {
} }
} }
fn load_soundtrack_items() -> AssetHandle<SoundtrackCollection> { fn load_soundtrack_items() -> AssetHandle<SoundtrackCollection<SoundtrackItem>> {
// Cannot fail: A default value is always provided // Cannot fail: A default value is always provided
SoundtrackCollection::load_expect("voxygen.audio.soundtrack") SoundtrackCollection::load_expect("voxygen.audio.soundtrack")
} }
} }
impl assets::Asset for SoundtrackCollection<RawSoundtrackItem> {
impl assets::Asset for SoundtrackCollection {
type Loader = assets::RonLoader; type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron"; const EXTENSION: &'static str = "ron";
}
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> { impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
warn!( fn load<S: assets::source::Source>(
"Error reading music config file, music will not be available: {:#?}", _: &assets::AssetCache<S>,
error id: &str,
); ) -> Result<Self, assets::Error> {
let inner = || -> Result<_, assets::Error> {
Ok(SoundtrackCollection::default()) let manifest: AssetHandle<assets::Ron<SoundtrackCollection<RawSoundtrackItem>>> =
AssetExt::load(id)?;
let mut soundtracks = SoundtrackCollection::default();
for item in manifest.read().0.tracks.iter().cloned() {
match item {
RawSoundtrackItem::Individual(track) => soundtracks.tracks.push(track),
RawSoundtrackItem::Segmented {
title,
timing,
biomes,
site,
segments,
} => {
for (path, length, activity) in segments.into_iter() {
soundtracks.tracks.push(SoundtrackItem {
title: title.clone(),
path,
length,
timing: timing.clone(),
biomes: biomes.clone(),
site,
activity,
});
}
},
}
}
Ok(soundtracks)
};
match inner() {
Ok(soundtracks) => Ok(soundtracks),
Err(e) => {
warn!("Error loading soundtracks: {:?}", e);
Ok(SoundtrackCollection::default())
},
}
} }
} }