Merge pull request #8876 from pchote/music
Refine the music playlist code.
This commit is contained in:
@@ -15,8 +15,10 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
public class MusicInfo
|
||||
{
|
||||
public readonly string Filename = null;
|
||||
public readonly string Title = null;
|
||||
public readonly string Filename;
|
||||
public readonly string Title;
|
||||
public readonly bool Hidden;
|
||||
|
||||
public int Length { get; private set; } // seconds
|
||||
public bool Exists { get; private set; }
|
||||
|
||||
@@ -25,17 +27,23 @@ namespace OpenRA.GameRules
|
||||
Title = value.Value;
|
||||
|
||||
var nd = value.ToDictionary();
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
|
||||
if (!GlobalFileSystem.Exists(Filename))
|
||||
return;
|
||||
|
||||
Exists = true;
|
||||
using (var s = GlobalFileSystem.Open(Filename))
|
||||
{
|
||||
if (Filename.ToLowerInvariant().EndsWith("wav"))
|
||||
Length = (int)WavLoader.WaveLength(s);
|
||||
else
|
||||
Length = (int)AudLoader.SoundLength(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
@@ -45,10 +53,12 @@ namespace OpenRA.GameRules
|
||||
|
||||
Exists = true;
|
||||
using (var s = GlobalFileSystem.Open(Filename))
|
||||
{
|
||||
if (Filename.ToLowerInvariant().EndsWith("wav"))
|
||||
Length = (int)WavLoader.WaveLength(s);
|
||||
else
|
||||
Length = (int)AudLoader.SoundLength(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,6 @@
|
||||
<Compile Include="Traits\TraitsInterfaces.cs" />
|
||||
<Compile Include="Traits\Util.cs" />
|
||||
<Compile Include="Traits\ValidateOrder.cs" />
|
||||
<Compile Include="Traits\World\MusicPlaylist.cs" />
|
||||
<Compile Include="Traits\World\Faction.cs" />
|
||||
<Compile Include="Traits\World\ResourceType.cs" />
|
||||
<Compile Include="Traits\World\ScreenShaker.cs" />
|
||||
|
||||
@@ -702,6 +702,7 @@
|
||||
<Compile Include="Traits\RevealsShroud.cs" />
|
||||
<Compile Include="Lint\CheckRevealFootprint.cs" />
|
||||
<Compile Include="Traits\ThrowsShrapnel.cs" />
|
||||
<Compile Include="Traits\World\MusicPlaylist.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -17,6 +17,7 @@ using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Trait for music handling. Attach this to the world actor.")]
|
||||
public class MusicPlaylistInfo : ITraitInfo
|
||||
@@ -20,20 +21,15 @@ namespace OpenRA.Traits
|
||||
[Desc("Music to play when the map starts.", "Plays the first song on the playlist when undefined.")]
|
||||
public readonly string StartingMusic = null;
|
||||
|
||||
[Desc("Should the starting music loop?")]
|
||||
public readonly bool LoopStartingMusic = false;
|
||||
|
||||
[Desc("Music to play when the game has been won.")]
|
||||
public readonly string VictoryMusic = null;
|
||||
|
||||
[Desc("Should the victory music loop?")]
|
||||
public readonly bool LoopVictoryMusic = false;
|
||||
|
||||
[Desc("Music to play when the game has been lost.")]
|
||||
public readonly string DefeatMusic = null;
|
||||
|
||||
[Desc("Should the defeat music loop?")]
|
||||
public readonly bool LoopDefeatMusic = false;
|
||||
[Desc("This track is played when no other music is playing.",
|
||||
"It cannot be paused, but can be overridden by selecting a new track.")]
|
||||
public readonly string BackgroundMusic = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new MusicPlaylist(init.World, this); }
|
||||
}
|
||||
@@ -41,44 +37,65 @@ namespace OpenRA.Traits
|
||||
public class MusicPlaylist : INotifyActorDisposing, IGameOver
|
||||
{
|
||||
readonly MusicPlaylistInfo info;
|
||||
readonly World world;
|
||||
|
||||
readonly MusicInfo[] random;
|
||||
readonly MusicInfo[] playlist;
|
||||
|
||||
public readonly bool IsMusicInstalled;
|
||||
public readonly bool IsMusicAvailable;
|
||||
public bool CurrentSongIsBackground { get; private set; }
|
||||
|
||||
MusicInfo currentSong;
|
||||
bool repeat;
|
||||
MusicInfo currentBackgroundSong;
|
||||
|
||||
public MusicPlaylist(World world, MusicPlaylistInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
this.world = world;
|
||||
|
||||
IsMusicAvailable = world.Map.Rules.InstalledMusic.Any();
|
||||
|
||||
playlist = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray();
|
||||
|
||||
if (!IsMusicAvailable)
|
||||
IsMusicInstalled = world.Map.Rules.InstalledMusic.Any();
|
||||
if (!IsMusicInstalled)
|
||||
return;
|
||||
|
||||
random = playlist.Shuffle(Game.CosmeticRandom).ToArray();
|
||||
playlist = world.Map.Rules.InstalledMusic
|
||||
.Where(a => !a.Value.Hidden)
|
||||
.Select(a => a.Value)
|
||||
.ToArray();
|
||||
|
||||
if (!string.IsNullOrEmpty(info.StartingMusic)
|
||||
&& world.Map.Rules.Music.ContainsKey(info.StartingMusic)
|
||||
&& world.Map.Rules.Music[info.StartingMusic].Exists)
|
||||
{
|
||||
random = playlist.Shuffle(Game.CosmeticRandom).ToArray();
|
||||
IsMusicAvailable = playlist.Any();
|
||||
|
||||
if (SongExists(info.StartingMusic))
|
||||
currentSong = world.Map.Rules.Music[info.StartingMusic];
|
||||
repeat = info.LoopStartingMusic;
|
||||
else if (SongExists(info.BackgroundMusic))
|
||||
{
|
||||
currentSong = currentBackgroundSong = world.Map.Rules.Music[info.BackgroundMusic];
|
||||
CurrentSongIsBackground = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSong = Game.Settings.Sound.Shuffle ? random.First() : playlist.First();
|
||||
repeat = Game.Settings.Sound.Repeat;
|
||||
// Start playback with a random song, but only if the player has installed more music
|
||||
var installData = Game.ModData.Manifest.Get<ContentInstaller>();
|
||||
if (playlist.Length > installData.ShippedSoundtracks)
|
||||
currentSong = random.FirstOrDefault();
|
||||
}
|
||||
|
||||
Play();
|
||||
}
|
||||
|
||||
bool SongExists(string song)
|
||||
{
|
||||
return !string.IsNullOrEmpty(song)
|
||||
&& world.Map.Rules.Music.ContainsKey(song)
|
||||
&& world.Map.Rules.Music[song].Exists;
|
||||
}
|
||||
|
||||
bool SongExists(MusicInfo song)
|
||||
{
|
||||
return song != null && song.Exists;
|
||||
}
|
||||
|
||||
public MusicInfo CurrentSong()
|
||||
{
|
||||
return currentSong;
|
||||
@@ -92,46 +109,34 @@ namespace OpenRA.Traits
|
||||
|
||||
public void GameOver(World world)
|
||||
{
|
||||
if (!IsMusicAvailable)
|
||||
return;
|
||||
|
||||
var playedSong = currentSong;
|
||||
|
||||
if (world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Won)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(info.VictoryMusic)
|
||||
&& world.Map.Rules.Music.ContainsKey(info.VictoryMusic)
|
||||
&& world.Map.Rules.Music[info.VictoryMusic].Exists)
|
||||
if (SongExists(info.VictoryMusic))
|
||||
{
|
||||
currentSong = world.Map.Rules.Music[info.VictoryMusic];
|
||||
repeat = info.LoopVictoryMusic;
|
||||
currentBackgroundSong = world.Map.Rules.Music[info.VictoryMusic];
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Most RTS treats observers losing the game,
|
||||
// no need for a special handling involving them here.
|
||||
if (!string.IsNullOrEmpty(info.DefeatMusic)
|
||||
&& world.Map.Rules.Music.ContainsKey(info.DefeatMusic)
|
||||
&& world.Map.Rules.Music[info.DefeatMusic].Exists)
|
||||
if (SongExists(info.DefeatMusic))
|
||||
{
|
||||
currentSong = world.Map.Rules.Music[info.DefeatMusic];
|
||||
repeat = info.LoopDefeatMusic;
|
||||
currentBackgroundSong = world.Map.Rules.Music[info.DefeatMusic];
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
if (playedSong != currentSong)
|
||||
Play();
|
||||
}
|
||||
|
||||
void Play()
|
||||
{
|
||||
if (currentSong == null || !IsMusicAvailable)
|
||||
if (!SongExists(currentSong))
|
||||
return;
|
||||
|
||||
Sound.PlayMusicThen(currentSong, () =>
|
||||
{
|
||||
if (!repeat)
|
||||
if (!CurrentSongIsBackground && !Game.Settings.Sound.Repeat)
|
||||
currentSong = GetNextSong();
|
||||
|
||||
Play();
|
||||
@@ -140,27 +145,22 @@ namespace OpenRA.Traits
|
||||
|
||||
public void Play(MusicInfo music)
|
||||
{
|
||||
if (music == null || !IsMusicAvailable)
|
||||
if (music == null)
|
||||
return;
|
||||
|
||||
currentSong = music;
|
||||
repeat = Game.Settings.Sound.Repeat;
|
||||
CurrentSongIsBackground = false;
|
||||
|
||||
Sound.PlayMusicThen(music, () =>
|
||||
{
|
||||
if (!repeat)
|
||||
currentSong = GetNextSong();
|
||||
|
||||
Play();
|
||||
});
|
||||
Play();
|
||||
}
|
||||
|
||||
public void Play(MusicInfo music, Action onComplete)
|
||||
{
|
||||
if (music == null || !IsMusicAvailable)
|
||||
if (music == null)
|
||||
return;
|
||||
|
||||
currentSong = music;
|
||||
CurrentSongIsBackground = false;
|
||||
Sound.PlayMusicThen(music, onComplete);
|
||||
}
|
||||
|
||||
@@ -181,22 +181,34 @@ namespace OpenRA.Traits
|
||||
|
||||
var songs = Game.Settings.Sound.Shuffle ? random : playlist;
|
||||
|
||||
return reverse ? songs.SkipWhile(m => m != currentSong)
|
||||
.Skip(1).FirstOrDefault() ?? songs.FirstOrDefault() :
|
||||
songs.Reverse().SkipWhile(m => m != currentSong)
|
||||
.Skip(1).FirstOrDefault() ?? songs.Reverse().FirstOrDefault();
|
||||
var next = reverse ? songs.Reverse().SkipWhile(m => m != currentSong)
|
||||
.Skip(1).FirstOrDefault() ?? songs.Reverse().FirstOrDefault() :
|
||||
songs.SkipWhile(m => m != currentSong)
|
||||
.Skip(1).FirstOrDefault() ?? songs.FirstOrDefault();
|
||||
|
||||
if (SongExists(next))
|
||||
return next;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
currentSong = null;
|
||||
Sound.StopMusic();
|
||||
|
||||
if (currentBackgroundSong != null)
|
||||
{
|
||||
currentSong = currentBackgroundSong;
|
||||
CurrentSongIsBackground = true;
|
||||
Play();
|
||||
}
|
||||
}
|
||||
|
||||
public void Disposing(Actor self)
|
||||
{
|
||||
if (currentSong != null)
|
||||
Stop();
|
||||
Sound.StopMusic();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,7 +560,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
musicButton.OnClick = () => Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs
|
||||
{
|
||||
{ "onExit", DoNothing },
|
||||
{ "world", orderManager.World }
|
||||
{ "world", worldRenderer.World }
|
||||
});
|
||||
|
||||
var settingsButton = lobby.GetOrNull<ButtonWidget>("SETTINGS_BUTTON");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
@@ -35,8 +35,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
BuildMusicTable();
|
||||
|
||||
Func<bool> noMusic = () => !musicPlaylist.IsMusicAvailable;
|
||||
panel.Get("NO_MUSIC_LABEL").IsVisible = noMusic;
|
||||
Func<bool> noMusic = () => !musicPlaylist.IsMusicAvailable || musicPlaylist.CurrentSongIsBackground || currentSong == null;
|
||||
panel.Get("NO_MUSIC_LABEL").IsVisible = () => !musicPlaylist.IsMusicAvailable;
|
||||
|
||||
var playButton = panel.Get<ButtonWidget>("BUTTON_PLAY");
|
||||
playButton.OnClick = Play;
|
||||
@@ -63,14 +63,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var shuffleCheckbox = panel.Get<CheckboxWidget>("SHUFFLE");
|
||||
shuffleCheckbox.IsChecked = () => Game.Settings.Sound.Shuffle;
|
||||
shuffleCheckbox.OnClick = () => Game.Settings.Sound.Shuffle ^= true;
|
||||
shuffleCheckbox.IsDisabled = () => musicPlaylist.CurrentSongIsBackground;
|
||||
|
||||
var repeatCheckbox = panel.Get<CheckboxWidget>("REPEAT");
|
||||
repeatCheckbox.IsChecked = () => Game.Settings.Sound.Repeat;
|
||||
repeatCheckbox.OnClick = () => Game.Settings.Sound.Repeat ^= true;
|
||||
repeatCheckbox.IsDisabled = () => musicPlaylist.CurrentSongIsBackground;
|
||||
|
||||
panel.Get<LabelWidget>("TIME_LABEL").GetText = () => (currentSong == null) ? "" :
|
||||
"{0:D2}:{1:D2} / {2:D2}:{3:D2}".F((int)Sound.MusicSeekPosition / 60, (int)Sound.MusicSeekPosition % 60,
|
||||
currentSong.Length / 60, currentSong.Length % 60);
|
||||
panel.Get<LabelWidget>("TIME_LABEL").GetText = () =>
|
||||
{
|
||||
if (currentSong == null || musicPlaylist.CurrentSongIsBackground)
|
||||
return "";
|
||||
|
||||
var minutes = (int)Sound.MusicSeekPosition / 60;
|
||||
var seconds = (int)Sound.MusicSeekPosition % 60;
|
||||
var totalMinutes = currentSong.Length / 60;
|
||||
var totalSeconds = currentSong.Length % 60;
|
||||
|
||||
return "{0:D2}:{1:D2} / {2:D2}:{3:D2}".F(minutes, seconds, totalMinutes, totalSeconds);
|
||||
};
|
||||
|
||||
var musicSlider = panel.Get<SliderWidget>("MUSIC_SLIDER");
|
||||
musicSlider.OnChange += x => Sound.MusicVolume = x;
|
||||
@@ -94,7 +105,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
songWatcher.OnTick = () =>
|
||||
{
|
||||
if (Sound.CurrentMusic == null || currentSong == Sound.CurrentMusic)
|
||||
if (musicPlaylist.CurrentSongIsBackground && currentSong != null)
|
||||
currentSong = null;
|
||||
|
||||
if (Sound.CurrentMusic == null || currentSong == Sound.CurrentMusic || musicPlaylist.CurrentSongIsBackground)
|
||||
return;
|
||||
|
||||
currentSong = Sound.CurrentMusic;
|
||||
@@ -111,23 +125,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
var music = musicPlaylist.AvailablePlaylist();
|
||||
currentSong = musicPlaylist.CurrentSong();
|
||||
if (currentSong == null && music.Any())
|
||||
currentSong = musicPlaylist.GetNextSong();
|
||||
|
||||
musicList.RemoveChildren();
|
||||
foreach (var s in music)
|
||||
{
|
||||
var song = s;
|
||||
if (currentSong == null)
|
||||
currentSong = song;
|
||||
|
||||
var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong == song, () => { currentSong = song; Play(); }, () => { });
|
||||
item.Get<LabelWidget>("TITLE").GetText = () => song.Title;
|
||||
item.Get<LabelWidget>("LENGTH").GetText = () => SongLengthLabel(song);
|
||||
musicList.AddChild(item);
|
||||
}
|
||||
|
||||
if (currentSong != null)
|
||||
if (currentSong != null && !musicPlaylist.CurrentSongIsBackground)
|
||||
musicList.ScrollToItem(currentSong.Filename);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ fwp: Fight Win Prevail
|
||||
fist226m: Iron Fist
|
||||
warfare: Warfare (Full Stop)
|
||||
win1: Great Shot!
|
||||
Hidden: true
|
||||
win2: Great Shot! (Voiced)
|
||||
Filename: win2
|
||||
Extension: var
|
||||
@@ -41,6 +42,7 @@ justdoit2: Just Do it Up (Voiced)
|
||||
Filename: justdoit
|
||||
Extension: var
|
||||
map1: Map Theme
|
||||
Hidden: true
|
||||
march: March to Doom
|
||||
nomercy: No Mercy
|
||||
nomercy2: No Mercy (Voiced)
|
||||
@@ -58,5 +60,9 @@ target: Target (Mechanical Man)
|
||||
j1: Untamed Land
|
||||
valkyrie: Ride of the Valkyries
|
||||
voic226m: Voice Rhythm
|
||||
outtakes: Outtakes
|
||||
Hidden: true
|
||||
nod_map1: Nod Map Theme
|
||||
nod_win1: Nod Win Theme
|
||||
Hidden: true
|
||||
nod_win1: Nod Win Theme
|
||||
Hidden: true
|
||||
|
||||
@@ -995,8 +995,7 @@ Rules:
|
||||
LuaScript:
|
||||
Scripts: shellmap.lua
|
||||
MusicPlaylist:
|
||||
StartingMusic: map1
|
||||
LoopStartingMusic: True
|
||||
BackgroundMusic: map1
|
||||
LST:
|
||||
Mobile:
|
||||
Speed: 42
|
||||
|
||||
@@ -17,6 +17,7 @@ LANDSAND: Land of Sand
|
||||
Extension: AUD
|
||||
OPTIONS: Options
|
||||
Extension: AUD
|
||||
Hidden: true
|
||||
PLOTTING: Plotting
|
||||
Extension: AUD
|
||||
RISEHARK: Rise of Harkonnen
|
||||
@@ -25,6 +26,7 @@ ROBOTIX: Robotix
|
||||
Extension: AUD
|
||||
SCORE: Score
|
||||
Extension: AUD
|
||||
Hidden: true
|
||||
SOLDAPPR: The Soldiers Approach
|
||||
Extension: AUD
|
||||
SPICESCT: Spice Scouting
|
||||
|
||||
@@ -124,8 +124,7 @@ Rules:
|
||||
Minimum: 1
|
||||
Maximum: 1
|
||||
MusicPlaylist:
|
||||
StartingMusic: options
|
||||
LoopStartingMusic: True
|
||||
BackgroundMusic: options
|
||||
rockettower:
|
||||
Power:
|
||||
Amount: 100
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
intro: Intro
|
||||
Hidden: true
|
||||
map: Map
|
||||
Hidden: true
|
||||
score: Militant Force
|
||||
Hidden: true
|
||||
await_r: Await
|
||||
bigf226m: Bigfoot
|
||||
credits: End Credits
|
||||
credits: Reload Fire
|
||||
crus226m: Crush
|
||||
dense_r: Dense
|
||||
fac1226m: Face to the Enemy 1
|
||||
|
||||
@@ -1255,6 +1255,8 @@ Rules:
|
||||
-CrateSpawner:
|
||||
-SpawnMPUnits:
|
||||
-MPStartLocations:
|
||||
MusicPlaylist:
|
||||
BackgroundMusic: intro
|
||||
ResourceType@ore:
|
||||
ValuePerUnit: 0
|
||||
LuaScript:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#Tiberian Sun
|
||||
intro: Intro
|
||||
Hidden: true
|
||||
approach: Approach
|
||||
defense: The Defense
|
||||
duskhour: Dusk Hour
|
||||
@@ -14,12 +15,14 @@ nodcrush: Nod Crush
|
||||
pharotek: Pharotek
|
||||
redsky: Red Sky
|
||||
score: Score
|
||||
Hidden: true
|
||||
scout: Scouting
|
||||
storm: Storm
|
||||
timebomb: Timebomb
|
||||
valves1b: Valves
|
||||
whatlurk: What Lurks
|
||||
maps: Map Selection
|
||||
Hidden: true
|
||||
#Firestorm
|
||||
dmachine: Deploy Machines
|
||||
elusive: Elusive
|
||||
|
||||
@@ -41,8 +41,7 @@ Rules:
|
||||
-SpawnMPUnits:
|
||||
-MPStartLocations:
|
||||
MusicPlaylist:
|
||||
StartingMusic: intro
|
||||
LoopStartingMusic: True
|
||||
BackgroundMusic: intro
|
||||
|
||||
Sequences:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user