diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs
index 9f3ef7729e..9088610862 100644
--- a/OpenRA.Game/Game.cs
+++ b/OpenRA.Game/Game.cs
@@ -286,7 +286,6 @@ namespace OpenRA
Console.WriteLine("Loading mod: {0}", mod);
Settings.Game.Mod = mod;
- Sound.StopMusic();
Sound.StopVideo();
Sound.Initialize();
diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index 29f7099e1d..d7ea45367c 100644
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -182,6 +182,7 @@
+
diff --git a/OpenRA.Game/Traits/World/MusicPlaylist.cs b/OpenRA.Game/Traits/World/MusicPlaylist.cs
new file mode 100644
index 0000000000..49201338f0
--- /dev/null
+++ b/OpenRA.Game/Traits/World/MusicPlaylist.cs
@@ -0,0 +1,150 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using OpenRA.GameRules;
+
+namespace OpenRA.Traits
+{
+ [Desc("Trait for music handling. Attach this to the world actor.")]
+ public class MusicPlaylistInfo : ITraitInfo
+ {
+ public readonly string StartingMusic = null;
+ public readonly bool LoopStartingMusic = false;
+
+ public object Create(ActorInitializer init) { return new MusicPlaylist(init.World, this); }
+ }
+
+ public class MusicPlaylist : INotifyActorDisposing
+ {
+ readonly MusicInfo[] random;
+ readonly MusicInfo[] playlist;
+
+ public readonly bool IsMusicAvailable;
+
+ MusicInfo currentSong;
+ bool repeat;
+
+ public MusicPlaylist(World world, MusicPlaylistInfo info)
+ {
+ IsMusicAvailable = world.Map.Rules.InstalledMusic.Any();
+
+ playlist = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray();
+
+ if (!IsMusicAvailable)
+ return;
+
+ random = playlist.Shuffle(Game.CosmeticRandom).ToArray();
+
+ if (Game.Settings.Sound.MapMusic
+ && !string.IsNullOrEmpty(info.StartingMusic)
+ && world.Map.Rules.Music.ContainsKey(info.StartingMusic)
+ && world.Map.Rules.Music[info.StartingMusic].Exists)
+ {
+ currentSong = world.Map.Rules.Music[info.StartingMusic];
+ repeat = info.LoopStartingMusic;
+ }
+ else
+ {
+ currentSong = Game.Settings.Sound.Shuffle ? random.First() : playlist.First();
+ repeat = Game.Settings.Sound.Repeat;
+ }
+
+ Play();
+ }
+
+ public MusicInfo CurrentSong()
+ {
+ return currentSong;
+ }
+
+ public MusicInfo[] AvailablePlaylist()
+ {
+ // TO-DO: add filter options for Race-specific music
+ return playlist;
+ }
+
+ void Play()
+ {
+ if (currentSong == null || !IsMusicAvailable)
+ return;
+
+ Sound.PlayMusicThen(currentSong, () =>
+ {
+ if (!repeat)
+ currentSong = GetNextSong();
+
+ Play();
+ });
+ }
+
+ public void Play(MusicInfo music)
+ {
+ if (music == null || !IsMusicAvailable)
+ return;
+
+ currentSong = music;
+ repeat = Game.Settings.Sound.Repeat;
+
+ Sound.PlayMusicThen(music, () =>
+ {
+ if (!repeat)
+ currentSong = GetNextSong();
+
+ Play();
+ });
+ }
+
+ public void Play(MusicInfo music, Action onComplete)
+ {
+ if (music == null || !IsMusicAvailable)
+ return;
+
+ currentSong = music;
+ Sound.PlayMusicThen(music, onComplete);
+ }
+
+ public MusicInfo GetNextSong()
+ {
+ return GetSong(false);
+ }
+
+ public MusicInfo GetPrevSong()
+ {
+ return GetSong(true);
+ }
+
+ MusicInfo GetSong(bool reverse)
+ {
+ if (!IsMusicAvailable)
+ return null;
+
+ 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();
+ }
+
+ public void Stop()
+ {
+ currentSong = null;
+ Sound.StopMusic();
+ }
+
+ public void Disposing(Actor self)
+ {
+ if (currentSong != null)
+ Stop();
+ }
+ }
+}
diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs
index 16ed7023d8..2413c73913 100644
--- a/OpenRA.Game/World.cs
+++ b/OpenRA.Game/World.cs
@@ -388,7 +388,6 @@ namespace OpenRA
frameEndActions.Clear();
Sound.StopAudio();
- Sound.StopMusic();
Sound.StopVideo();
// Dispose newer actors first, and the world actor last
diff --git a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs
index bcc0d41058..182a2ea815 100644
--- a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs
+++ b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs
@@ -11,26 +11,28 @@
using System;
using System.Drawing;
using System.IO;
-using System.Linq;
using Eluant;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
-using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Effects;
using OpenRA.Scripting;
+using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptGlobal("Media")]
public class MediaGlobal : ScriptGlobal
{
- World world;
+ readonly World world;
+ readonly MusicPlaylist playlist;
+
public MediaGlobal(ScriptContext context)
: base(context)
{
world = context.World;
+ playlist = world.WorldActor.Trait();
}
[Desc("Play an announcer voice listed in notifications.yaml")]
@@ -51,23 +53,15 @@ namespace OpenRA.Mods.Common.Scripting
Sound.Play(file);
}
- MusicInfo previousMusic;
Action onComplete;
[Desc("Play track defined in music.yaml or keep it empty for a random song.")]
public void PlayMusic(string track = null, LuaFunction func = null)
{
- if (!Game.Settings.Sound.MapMusic)
- return;
-
- var music = world.Map.Rules.InstalledMusic.Select(a => a.Value).ToArray();
- if (!music.Any())
+ if (!Game.Settings.Sound.MapMusic || !playlist.IsMusicAvailable)
return;
var musicInfo = !string.IsNullOrEmpty(track) ? world.Map.Rules.Music[track]
- : Game.Settings.Sound.Repeat && previousMusic != null ? previousMusic
- : Game.Settings.Sound.Shuffle ? music.Random(Game.CosmeticRandom)
- : previousMusic == null ? music.First()
- : music.SkipWhile(s => s != previousMusic).Skip(1).First();
+ : playlist.GetNextSong();
if (func != null)
{
@@ -84,19 +78,17 @@ namespace OpenRA.Mods.Common.Scripting
Context.FatalError(e.Message);
}
};
+
+ playlist.Play(musicInfo, onComplete);
}
else
- onComplete = () => { };
-
- Sound.PlayMusicThen(musicInfo, onComplete);
-
- previousMusic = Sound.CurrentMusic;
+ playlist.Play(musicInfo);
}
[Desc("Stop the current song.")]
public void StopMusic()
{
- Sound.StopMusic();
+ playlist.Stop();
}
Action onCompleteFullscreen;
diff --git a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs
index 67e2a37b39..2830512b0e 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs
@@ -18,29 +18,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
public class MusicPlayerLogic
{
- readonly Ruleset modRules;
+ readonly ScrollPanelWidget musicList;
+ readonly ScrollItemWidget itemTemplate;
- bool installed;
+ readonly MusicPlaylist musicPlaylist;
MusicInfo currentSong = null;
- MusicInfo[] music;
- MusicInfo[] random;
- ScrollPanelWidget musicList;
-
- ScrollItemWidget itemTemplate;
[ObjectCreator.UseCtor]
public MusicPlayerLogic(Widget widget, Ruleset modRules, World world, Action onExit)
{
- this.modRules = modRules;
-
var panel = widget.Get("MUSIC_PANEL");
musicList = panel.Get("MUSIC_LIST");
itemTemplate = musicList.Get("MUSIC_TEMPLATE");
+ musicPlaylist = world.WorldActor.Trait();
BuildMusicTable();
- Func noMusic = () => !installed;
+ Func noMusic = () => !musicPlaylist.IsMusicAvailable;
panel.Get("NO_MUSIC_LABEL").IsVisible = noMusic;
var playButton = panel.Get("BUTTON_PLAY");
@@ -54,15 +49,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
pauseButton.IsVisible = () => Sound.MusicPlaying;
var stopButton = panel.Get("BUTTON_STOP");
- stopButton.OnClick = Sound.StopMusic;
+ stopButton.OnClick = () => { musicPlaylist.Stop(); };
stopButton.IsDisabled = noMusic;
var nextButton = panel.Get("BUTTON_NEXT");
- nextButton.OnClick = () => { currentSong = GetNextSong(); Play(); };
+ nextButton.OnClick = () => { currentSong = musicPlaylist.GetNextSong(); Play(); };
nextButton.IsDisabled = noMusic;
var prevButton = panel.Get("BUTTON_PREV");
- prevButton.OnClick = () => { currentSong = GetPrevSong(); Play(); };
+ prevButton.OnClick = () => { currentSong = musicPlaylist.GetPrevSong(); Play(); };
prevButton.IsDisabled = noMusic;
var shuffleCheckbox = panel.Get("SHUFFLE");
@@ -111,11 +106,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public void BuildMusicTable()
{
- music = modRules.InstalledMusic.Select(a => a.Value).ToArray();
- random = music.Shuffle(Game.CosmeticRandom).ToArray();
- currentSong = Sound.CurrentMusic;
+ if (!musicPlaylist.IsMusicAvailable)
+ return;
+
+ var music = musicPlaylist.AvailablePlaylist();
+ currentSong = musicPlaylist.CurrentSong();
if (currentSong == null && music.Any())
- currentSong = Game.Settings.Sound.Shuffle ? random.First() : music.First();
+ currentSong = musicPlaylist.GetNextSong();
musicList.RemoveChildren();
foreach (var s in music)
@@ -124,8 +121,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (currentSong == null)
currentSong = song;
- // TODO: We leak the currentSong MusicInfo across map load, so compare the Filename instead.
- var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong.Filename == song.Filename, () => { currentSong = song; Play(); }, () => { });
+ var item = ScrollItemWidget.Setup(song.Filename, itemTemplate, () => currentSong == song, () => { currentSong = song; Play(); }, () => { });
item.Get("TITLE").GetText = () => song.Title;
item.Get("LENGTH").GetText = () => SongLengthLabel(song);
musicList.AddChild(item);
@@ -133,8 +129,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (currentSong != null)
musicList.ScrollToItem(currentSong.Filename);
-
- installed = modRules.InstalledMusic.Any();
}
void Play()
@@ -143,38 +137,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return;
musicList.ScrollToItem(currentSong.Filename);
-
- Sound.PlayMusicThen(currentSong, () =>
- {
- if (!Game.Settings.Sound.Repeat)
- currentSong = GetNextSong();
- Play();
- });
+ musicPlaylist.Play(currentSong);
}
static string SongLengthLabel(MusicInfo song)
{
return "{0:D1}:{1:D2}".F(song.Length / 60, song.Length % 60);
}
-
- MusicInfo GetNextSong()
- {
- if (!music.Any())
- return null;
-
- var songs = Game.Settings.Sound.Shuffle ? random : music;
- return songs.SkipWhile(m => m != currentSong)
- .Skip(1).FirstOrDefault() ?? songs.FirstOrDefault();
- }
-
- MusicInfo GetPrevSong()
- {
- if (!music.Any())
- return null;
-
- var songs = Game.Settings.Sound.Shuffle ? random : music;
- return songs.Reverse().SkipWhile(m => m != currentSong)
- .Skip(1).FirstOrDefault() ?? songs.Reverse().FirstOrDefault();
- }
}
}