diff --git a/OpenRA.Game/Effects/AsyncAction.cs b/OpenRA.Game/Effects/AsyncAction.cs new file mode 100644 index 0000000000..42f36b63b4 --- /dev/null +++ b/OpenRA.Game/Effects/AsyncAction.cs @@ -0,0 +1,38 @@ +#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.Collections.Generic; +using OpenRA.Graphics; + +namespace OpenRA.Effects +{ + public class AsyncAction : IEffect + { + Action a; + IAsyncResult ar; + + public AsyncAction(IAsyncResult ar, Action a) + { + this.a = a; + this.ar = ar; + } + + public void Tick(World world) + { + if (ar.IsCompleted) + { + world.AddFrameEndTask(w => { w.Remove(this); a(); }); + } + } + + public IEnumerable Render(WorldRenderer r) { yield break; } + } +} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 30a70c20eb..9473294e05 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -88,6 +88,7 @@ + diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index cdb22d3dd8..3bb1c0c269 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -21,6 +21,7 @@ namespace OpenRA.Widgets public Hotkey CancelKey = new Hotkey(Keycode.ESCAPE, Modifiers.None); public float AspectRatio = 1.2f; public bool DrawOverlay = true; + public bool Skippable = true; public bool Paused { get { return paused; } } public VqaReader Video { get { return video; } } @@ -48,15 +49,21 @@ namespace OpenRA.Widgets { if (filename == cachedVideo) return; + var video = new VqaReader(GlobalFileSystem.Open(filename)); + + cachedVideo = filename; + Open(video); + } + + public void Open(VqaReader video) + { + this.video = video; stopped = true; paused = true; Sound.StopVideo(); onComplete = () => { }; - cachedVideo = filename; - video = new VqaReader(GlobalFileSystem.Open(filename)); - invLength = video.Framerate * 1f / video.Frames; var size = Math.Max(video.Width, video.Height); @@ -107,7 +114,8 @@ namespace OpenRA.Widgets else nextFrame = video.CurrentFrame + 1; - if (nextFrame > video.Frames) + // Without the 2nd check the sound playback sometimes ends before the final frame is displayed which causes the player to be stuck on the first frame + if (nextFrame > video.Frames || nextFrame < video.CurrentFrame) { Stop(); return; @@ -136,7 +144,7 @@ namespace OpenRA.Widgets public override bool HandleKeyPress(KeyInput e) { - if (Hotkey.FromKeyInput(e) != CancelKey || e.Event != KeyInputEvent.Down) + if (Hotkey.FromKeyInput(e) != CancelKey || e.Event != KeyInputEvent.Down || !Skippable) return false; Stop(); @@ -188,5 +196,11 @@ namespace OpenRA.Widgets videoSprite.Sheet.GetTexture().SetData(video.FrameData); world.AddFrameEndTask(_ => onComplete()); } + + public void CloseVideo() + { + Stop(); + video = null; + } } } diff --git a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs index 1f0d51cda9..8da6a551db 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs @@ -10,11 +10,15 @@ 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.Mods.Common.Effects; using OpenRA.Scripting; -using OpenRA.Traits; namespace OpenRA.Mods.Common.Scripting { @@ -40,33 +44,8 @@ namespace OpenRA.Mods.Common.Scripting Sound.PlayNotification(world.Map.Rules, player, "Sounds", notification, player != null ? player.Country.Race : null); } - Action onComplete; - [Desc("Play a VQA video including the file extension.")] - public void PlayMovieFullscreen(string movie, LuaFunction func = null) - { - if (func != null) - { - var f = func.CopyReference() as LuaFunction; - onComplete = () => - { - try - { - using (f) - f.Call().Dispose(); - } - catch (LuaException e) - { - Context.FatalError(e.Message); - } - }; - } - else - onComplete = () => { }; - - Media.PlayFMVFullscreen(world, movie, onComplete); - } - 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) { @@ -78,10 +57,10 @@ namespace OpenRA.Mods.Common.Scripting 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(); + : 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(); if (func != null) { @@ -103,6 +82,7 @@ namespace OpenRA.Mods.Common.Scripting onComplete = () => { }; Sound.PlayMusicThen(musicInfo, onComplete); + previousMusic = Sound.CurrentMusic; } @@ -112,13 +92,100 @@ namespace OpenRA.Mods.Common.Scripting Sound.StopMusic(); } + Action onCompleteFullscreen; + [Desc("Play a VQA video fullscreen. File name has to include the file extension.")] + public void PlayMovieFullscreen(string movie, LuaFunction func = null) + { + if (func != null) + { + var f = func.CopyReference() as LuaFunction; + onCompleteFullscreen = () => + { + try + { + using (f) + f.Call().Dispose(); + } + catch (LuaException e) + { + Context.FatalError(e.Message); + } + }; + } + else + onCompleteFullscreen = () => { }; + + Media.PlayFMVFullscreen(world, movie, onCompleteFullscreen); + } + + Action onLoadComplete; + Action onCompleteRadar; + [Desc("Play a VQA video in the radar window. File name has to include the file extension. Returns true on success, if the movie wasn't found the function returns false and the callback is executed.")] + public bool PlayMovieInRadar(string movie, LuaFunction playComplete = null) + { + if (playComplete != null) + { + var f = playComplete.CopyReference() as LuaFunction; + onCompleteRadar = () => + { + try + { + using (f) + f.Call().Dispose(); + } + catch (LuaException e) + { + Context.FatalError(e.Message); + } + }; + } + else + onCompleteRadar = () => { }; + + Stream s = null; + try + { + s = GlobalFileSystem.Open(movie); + } + catch (FileNotFoundException e) + { + Log.Write("lua", "Couldn't play movie {0}! File doesn't exist.", e.FileName); + onCompleteRadar(); + return false; + } + + AsyncLoader l = new AsyncLoader(Media.LoadVqa); + IAsyncResult ar = l.BeginInvoke(s, null, null); + onLoadComplete = () => + { + Media.StopFMVInRadar(); + world.AddFrameEndTask(_ => Media.PlayFMVInRadar(world, l.EndInvoke(ar), onCompleteRadar)); + }; + + world.AddFrameEndTask(w => w.Add(new AsyncAction(ar, onLoadComplete))); + return true; + } + [Desc("Display a text message to the player.")] - public void DisplayMessage(string text, string prefix = "Mission") // TODO: expose HSLColor to Lua and add as parameter + public void DisplayMessage(string text, string prefix = "Mission") { if (string.IsNullOrEmpty(text)) return; - Game.AddChatLine(Color.White, prefix, text); - } + Color c = Color.White; + Game.AddChatLine(c, prefix, text); + } // TODO: expose HSLColor to Lua and add as parameter + + [Desc("Display a text message at the specified location.")] + public void FloatingText(string text, WPos position, int duration = 30) + { + if (string.IsNullOrEmpty(text) || !world.Map.Contains(world.Map.CellContaining(position))) + return; + + Color c = Color.White; + world.AddFrameEndTask(w => w.Add(new FloatingText(position, c, text, duration))); + } // TODO: expose HSLColor to Lua and add as parameter + + public delegate VqaReader AsyncLoader(Stream s); } } diff --git a/OpenRA.Mods.Common/Scripting/Media.cs b/OpenRA.Mods.Common/Scripting/Media.cs index 30e74ace30..0365974830 100644 --- a/OpenRA.Mods.Common/Scripting/Media.cs +++ b/OpenRA.Mods.Common/Scripting/Media.cs @@ -10,6 +10,7 @@ using System; using System.IO; +using OpenRA.FileFormats; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Scripting @@ -56,5 +57,28 @@ namespace OpenRA.Mods.Common.Scripting onComplete(); }); } + + public static void PlayFMVInRadar(World w, VqaReader movie, Action onComplete) + { + var player = Ui.Root.Get("PLAYER"); + player.Open(movie); + + player.PlayThen(() => + { + onComplete(); + player.CloseVideo(); + }); + } + + public static void StopFMVInRadar() + { + var player = Ui.Root.Get("PLAYER"); + player.Stop(); + } + + public static VqaReader LoadVqa(Stream s) + { + return new VqaReader(s); + } } } diff --git a/mods/cnc/chrome/ingame.yaml b/mods/cnc/chrome/ingame.yaml index 63da31364c..01cb2840f3 100644 --- a/mods/cnc/chrome/ingame.yaml +++ b/mods/cnc/chrome/ingame.yaml @@ -314,6 +314,12 @@ Container@PLAYER_WIDGETS: WorldInteractionController: INTERACTION_CONTROLLER Children: LogicTicker@RADAR_TICKER: + VqaPlayer@PLAYER: + X: 1 + Y: 1 + Width: PARENT_RIGHT-2 + Height: PARENT_BOTTOM-2 + Skippable: false Background@POWERBAR_PANEL: Logic: IngamePowerBarLogic X: 4 diff --git a/mods/d2k/chrome/ingame-player.yaml b/mods/d2k/chrome/ingame-player.yaml index 49850b555b..0524631bbf 100644 --- a/mods/d2k/chrome/ingame-player.yaml +++ b/mods/d2k/chrome/ingame-player.yaml @@ -161,6 +161,13 @@ Container@PLAYER_WIDGETS: Y: 34 Width: 202 Height: 202 + Children: + VqaPlayer@PLAYER: + X: 12 + Y: 32 + Width: 202 + Height: 202 + Skippable: false Image@MINIMAP: X: 12 Y: 34 diff --git a/mods/ra/chrome/ingame-player.yaml b/mods/ra/chrome/ingame-player.yaml index b36e7db86f..6beded3cd2 100644 --- a/mods/ra/chrome/ingame-player.yaml +++ b/mods/ra/chrome/ingame-player.yaml @@ -161,6 +161,13 @@ Container@PLAYER_WIDGETS: Y: 41 Width: 220 Height: 220 + Children: + VqaPlayer@PLAYER: + X: 8 + Y: 40 + Width: 220 + Height: 220 + Skippable: false Label@GAME_TIMER: Logic: GameTimerLogic X: 3 @@ -369,4 +376,4 @@ Container@PLAYER_WIDGETS: X: 6 Y: 3 ImageCollection: scrollbar - ImageName: down_arrow \ No newline at end of file + ImageName: down_arrow diff --git a/mods/ts/chrome/ingame-player.yaml b/mods/ts/chrome/ingame-player.yaml index 5ad92d9205..256637f409 100644 --- a/mods/ts/chrome/ingame-player.yaml +++ b/mods/ts/chrome/ingame-player.yaml @@ -51,6 +51,11 @@ Container@PLAYER_WIDGETS: X: 9 Width: 192 Height: 192 + VqaPlayer@PLAYER: + X: 9 + Width: 192 + Height: 192 + Skippable: false ResourceBar@POWERBAR: Logic: IngamePowerBarLogic X: 42