Add new version of combat soundtrack, and change details of how transitions between tracks work.

This commit is contained in:
Avi Weinstock 2021-04-07 15:53:41 -04:00
parent 0e16c23707
commit f1b022cdee
17 changed files with 120 additions and 52 deletions

View File

@ -0,0 +1,6 @@
(
combat_nearby_radius: 40.0,
combat_health_factor: 1000,
combat_nearby_high_thresh: 4,
combat_nearby_low_thresh: 1,
)

View File

@ -374,21 +374,20 @@
artist: "badbbad", artist: "badbbad",
)), )),
Segmented( Segmented(
title: "Combat1", title: "Barred Paths",
author: "DaforLynx", author: "DaforLynx",
timing: None, timing: None,
biomes: [], biomes: [],
site: Some(Dungeon), site: Some(Dungeon),
segments: [ segments: [
("voxygen.audio.soundtrack.combat1.combat1-hi-loop", 54.0, State(Combat(High))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-hi-end", 6.0, Transition(Combat(High), Explore), None),
("voxygen.audio.soundtrack.combat1.combat1-hi-start", 55.0, Transition(Explore, Combat(High))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-hi-loop", 54.0, State(Combat(High)), None),
("voxygen.audio.soundtrack.combat1.combat1-lo-loop", 7.0, State(Combat(Low))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-hi-start", 55.0, Transition(Explore, Combat(High)), Some(Combat(High))),
("voxygen.audio.soundtrack.combat1.combat1-lo-start", 10.0, Transition(Explore, Combat(Low))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-lo-end", 3.0, Transition(Combat(Low), Explore), None),
("voxygen.audio.soundtrack.combat1.combat1-trans-hi-lo", 10.0, Transition(Combat(High), Combat(Low))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-lo-loop", 7.0, State(Combat(Low)), None),
("voxygen.audio.soundtrack.combat1.combat1-trans-lo-hi", 7.0, Transition(Combat(Low), Combat(High))), ("voxygen.audio.soundtrack.barred_paths.barred_paths-lo-start", 10.0, Transition(Explore, Combat(Low)), None),
// temporary until more assets exist: ("voxygen.audio.soundtrack.barred_paths.barred_paths-trans-hi-lo", 10.0, Transition(Combat(High), Combat(Low)), None),
("voxygen.audio.soundtrack.combat1.combat1-trans-hi-lo", 10.0, Transition(Combat(High), Explore)), ("voxygen.audio.soundtrack.barred_paths.barred_paths-trans-lo-hi", 7.0, Transition(Combat(Low), Combat(High)), None),
("voxygen.audio.soundtrack.combat1.combat1-lo-loop", 7.0, Transition(Combat(Low), Explore)),
], ],
), ),
] ]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -50,6 +50,7 @@ use common::{
terrain::{BiomeKind, SitesKind}, terrain::{BiomeKind, SitesKind},
}; };
use common_sys::state::State; use common_sys::state::State;
use lazy_static::lazy_static;
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;
@ -84,6 +85,11 @@ pub struct SoundtrackItem {
/// What the player is doing when the track is played (i.e. exploring, /// What the player is doing when the track is played (i.e. exploring,
/// combat) /// combat)
activity: MusicActivity, activity: MusicActivity,
/// What activity to override the activity state with, if any (e.g. to make
/// a long combat intro also act like the loop for the purposes of outro
/// transitions)
#[serde(default)]
activity_override: Option<MusicActivityState>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@ -94,7 +100,7 @@ enum RawSoundtrackItem {
timing: Option<DayPeriod>, timing: Option<DayPeriod>,
biomes: Vec<(BiomeKind, u8)>, biomes: Vec<(BiomeKind, u8)>,
site: Option<SitesKind>, site: Option<SitesKind>,
segments: Vec<(String, f32, MusicActivity)>, segments: Vec<(String, f32, MusicActivity, Option<MusicActivityState>)>,
}, },
} }
@ -146,7 +152,47 @@ pub struct MusicMgr {
/// 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 /// The previous track's activity kind, for transitions
last_activity: MusicActivityState, last_activity: MusicActivity,
}
#[derive(Deserialize)]
struct MusicTransitionManifest {
/// Within what radius do enemies count towards combat music?
combat_nearby_radius: f32,
/// Each multiple of this factor that an enemy has health counts as an extra
/// enemy
combat_health_factor: u32,
/// How many nearby enemies trigger High combat music
combat_nearby_high_thresh: u32,
/// How many nearby enemies trigger Low combat music
combat_nearby_low_thresh: u32,
}
impl Default for MusicTransitionManifest {
fn default() -> MusicTransitionManifest {
MusicTransitionManifest {
combat_nearby_radius: 40.0,
combat_health_factor: 1000,
combat_nearby_high_thresh: 3,
combat_nearby_low_thresh: 1,
}
}
}
impl assets::Asset for MusicTransitionManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
fn default_value(id: &str, e: assets::Error) -> Result<MusicTransitionManifest, assets::Error> {
warn!("Error loading MusicTransitionManifest {:?}: {:?}", id, e);
Ok(MusicTransitionManifest::default())
}
}
lazy_static! {
static ref MUSIC_TRANSITION_MANIFEST: AssetHandle<MusicTransitionManifest> =
AssetExt::load_expect("voxygen.audio.music_transition_manifest");
} }
impl Default for MusicMgr { impl Default for MusicMgr {
@ -156,7 +202,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, last_activity: MusicActivity::State(MusicActivityState::Explore),
} }
} }
} }
@ -185,34 +231,41 @@ impl MusicMgr {
let positions = ecs.read_component::<Pos>(); let positions = ecs.read_component::<Pos>();
let healths = ecs.read_component::<Health>(); let healths = ecs.read_component::<Health>();
let groups = ecs.read_component::<Group>(); let groups = ecs.read_component::<Group>();
let mtm = MUSIC_TRANSITION_MANIFEST.read();
if let Some(player_pos) = positions.get(player) { if let Some(player_pos) = positions.get(player) {
const NEARBY_RADIUS: f32 = 50.0;
const HEALTH_FACTOR: u32 = 100;
let num_nearby_entities: u32 = (&entities, &positions, &healths, &groups) let num_nearby_entities: u32 = (&entities, &positions, &healths, &groups)
.join() .join()
.map(|(entity, pos, health, group)| { .map(|(entity, pos, health, group)| {
if entity != player if entity != player
&& group == &ENEMY && group == &ENEMY
&& (player_pos.0 - pos.0).magnitude_squared() < NEARBY_RADIUS.powf(2.0) && (player_pos.0 - pos.0).magnitude_squared()
< mtm.combat_nearby_radius.powf(2.0)
{ {
(health.maximum() / HEALTH_FACTOR).max(1) (health.maximum() / mtm.combat_health_factor).max(1)
} else { } else {
0 0
} }
}) })
.sum(); .sum();
if num_nearby_entities > 2 { if num_nearby_entities >= mtm.combat_nearby_high_thresh {
activity_state = Combat(CombatIntensity::High); activity_state = Combat(CombatIntensity::High);
} else if num_nearby_entities >= 1 { } else if num_nearby_entities >= mtm.combat_nearby_low_thresh {
activity_state = Combat(CombatIntensity::Low); activity_state = Combat(CombatIntensity::Low);
} }
trace!(
"in audio maintain: {:?} {:?}",
activity_state,
num_nearby_entities
);
} }
let activity = if self.last_activity != activity_state { let activity = match self.last_activity {
MusicActivity::Transition(self.last_activity, activity_state) MusicActivity::State(prev) if prev != activity_state => {
} else { MusicActivity::Transition(prev, activity_state)
MusicActivity::State(activity_state) },
MusicActivity::Transition(_, next) => MusicActivity::State(next),
_ => MusicActivity::State(activity_state),
}; };
let interrupt = matches!(activity, MusicActivity::Transition(_, _)); let interrupt = matches!(activity, MusicActivity::Transition(_, _));
@ -221,9 +274,13 @@ impl MusicMgr {
&& !self.soundtrack.read().tracks.is_empty() && !self.soundtrack.read().tracks.is_empty()
&& (self.began_playing.elapsed().as_secs_f32() > self.next_track_change || interrupt) && (self.began_playing.elapsed().as_secs_f32() > self.next_track_change || interrupt)
{ {
trace!("in audio maintain: {:?} {:?}", self.last_activity, activity); trace!(
if let Ok(()) = self.play_random_track(audio, state, client, &activity) { "pre-play_random_track: {:?} {:?}",
self.last_activity = activity_state; self.last_activity,
activity
);
if let Ok(next_activity) = self.play_random_track(audio, state, client, &activity) {
self.last_activity = next_activity;
} }
} }
} }
@ -234,7 +291,7 @@ impl MusicMgr {
state: &State, state: &State,
client: &Client, client: &Client,
activity: &MusicActivity, activity: &MusicActivity,
) -> Result<(), ()> { ) -> Result<MusicActivity, ()> {
let mut rng = thread_rng(); let mut rng = thread_rng();
// Adds a bit of randomness between plays // Adds a bit of randomness between plays
@ -254,7 +311,7 @@ impl MusicMgr {
// too many constraints. Returning Err(()) signals that we couldn't find // too many constraints. Returning Err(()) signals that we couldn't find
// an appropriate track for the current state, and hence the state // an appropriate track for the current state, and hence the state
// machine for the activity shouldn't be updated. // machine for the activity shouldn't be updated.
let mut res = Ok(()); let mut res = Ok(*activity);
let soundtrack = self.soundtrack.read(); let soundtrack = self.soundtrack.read();
// First, filter out tracks not matching the timing, site, and biome // First, filter out tracks not matching the timing, site, and biome
let mut maybe_tracks = soundtrack let mut maybe_tracks = soundtrack
@ -339,16 +396,15 @@ impl MusicMgr {
); );
if let Ok(track) = new_maybe_track { if let Ok(track) = new_maybe_track {
if let Some(state) = track.activity_override {
res = Ok(MusicActivity::State(state));
}
//println!("Now playing {:?}", track.title); //println!("Now playing {:?}", track.title);
self.last_track = String::from(&track.title); self.last_track = String::from(&track.title);
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;
let tag = if matches!( let tag = if matches!(activity, MusicActivity::State(MusicActivityState::Explore)) {
activity,
MusicActivity::State(MusicActivityState::Explore)
| MusicActivity::Transition(_, MusicActivityState::Explore)
) {
MusicChannelTag::Exploration MusicChannelTag::Exploration
} else { } else {
MusicChannelTag::Combat MusicChannelTag::Combat
@ -398,7 +454,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
site, site,
segments, segments,
} => { } => {
for (path, length, activity) in segments.into_iter() { for (path, length, activity, activity_override) in segments.into_iter() {
soundtracks.tracks.push(SoundtrackItem { soundtracks.tracks.push(SoundtrackItem {
title: title.clone(), title: title.clone(),
path, path,
@ -407,6 +463,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
biomes: biomes.clone(), biomes: biomes.clone(),
site, site,
activity, activity,
activity_override,
}); });
} }
}, },