From a3bf3e7403297951313b4ec32ff946223b0c8fc8 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Sun, 27 Jul 2014 11:00:55 +0200 Subject: [PATCH 01/13] Add support for mission objectives Objectives can be either primary or secondary objectives. Primary ones influence the outcome of the game. If all primary objectives are completed the game is won, and lost when any of them fails. Objectives can be added at any stage during the game, allowing to react dynamically to game events. The objectives backend only contains the information about the objectives themselves. It does not check if objectives are completed or failed. Instead, the state of objectives must be manually marked. The backend, however, does check whether the game is won or lost. --- OpenRA.Game/Traits/TraitsInterfaces.cs | 9 + OpenRA.Game/World.cs | 13 +- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + OpenRA.Mods.RA/Player/MissionObjectives.cs | 202 +++++++++++++++++++++ mods/cnc/rules/player.yaml | 1 + mods/d2k/rules/player.yaml | 1 + mods/ra/rules/player.yaml | 1 + mods/ts/rules/player.yaml | 1 + 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 OpenRA.Mods.RA/Player/MissionObjectives.cs diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 780673dd7f..f5dfce3daf 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -273,6 +273,15 @@ namespace OpenRA.Traits public interface IObjectivesPanel { string ObjectivesPanel { get; } } + public interface INotifyObjectivesUpdated + { + void OnPlayerWon(Player winner); + void OnPlayerLost(Player loser); + void OnObjectiveAdded(Player player, int objectiveID); + void OnObjectiveCompleted(Player player, int objectiveID); + void OnObjectiveFailed(Player player, int objectiveID); + } + public static class DisableExts { public static bool IsDisabled(this Actor a) diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index d58d68e474..a2ac5c1ca4 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -42,8 +42,19 @@ namespace OpenRA public void AddPlayer(Player p) { Players.Add(p); } public Player LocalPlayer { get; private set; } - Player renderPlayer; + public event Action GameOver = () => { }; + bool gameOver; + public void EndGame() + { + if (!gameOver) + { + gameOver = true; + GameOver(); + } + } + public bool ObserveAfterWinOrLose; + Player renderPlayer; public Player RenderPlayer { get { return renderPlayer == null || (ObserveAfterWinOrLose && renderPlayer.WinState != WinState.Undefined) ? null : renderPlayer; } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 6c4548cde7..bc21912282 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -284,6 +284,7 @@ + diff --git a/OpenRA.Mods.RA/Player/MissionObjectives.cs b/OpenRA.Mods.RA/Player/MissionObjectives.cs new file mode 100644 index 0000000000..7fdbcf42c5 --- /dev/null +++ b/OpenRA.Mods.RA/Player/MissionObjectives.cs @@ -0,0 +1,202 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Collections.Generic; +using System.Linq; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public enum ObjectiveType { Primary, Secondary }; + public enum ObjectiveState { Incomplete, Completed, Failed }; + + public class MissionObjective + { + public readonly ObjectiveType Type; + public readonly string Description; + public ObjectiveState State; + + public MissionObjective(ObjectiveType type, string description) + { + Type = type; + Description = description; + State = ObjectiveState.Incomplete; + } + } + + public class MissionObjectivesInfo : ITraitInfo + { + [Desc("Set this to true if multiple cooperative players have a distinct set of " + + "objectives that each of them has to complete to win the game. This is mainly " + + "useful for multiplayer coop missions. Do not use this for skirmish team games.")] + public readonly bool Cooperative = false; + + [Desc("If set to true, this setting causes the game to end immediately once the first " + + "player (or team of cooperative players) fails or completes his objectives. If " + + "set to false, players that fail their objectives will stick around and become observers.")] + public readonly bool EarlyGameOver = false; + + [Desc("Delay between the game over condition being met, and the game actually ending, in milliseconds.")] + public readonly int GameOverDelay = 1500; + + public object Create(ActorInitializer init) { return new MissionObjectives(init.world, this); } + } + + public class MissionObjectives : INotifyObjectivesUpdated, ISync + { + readonly MissionObjectivesInfo info; + readonly List objectives = new List(); + public ReadOnlyList Objectives; + + [Sync] + public int ObjectivesHash { get { return Objectives.Aggregate(0, (code, objective) => code ^ Sync.hash(objective.State)); } } + + // This property is used as a flag in 'Cooperative' games to mark that the player has completed all his objectives. + // The player's WinState is only updated when his allies have all completed their objective as well. + public WinState WinStateCooperative { get; private set; } + + public MissionObjectives(World world, MissionObjectivesInfo moInfo) + { + info = moInfo; + Objectives = new ReadOnlyList(objectives); + + world.ObserveAfterWinOrLose = !info.EarlyGameOver; + } + + public int Add(Player player, string description, ObjectiveType type = ObjectiveType.Primary) + { + var newID = objectives.Count; + + objectives.Insert(newID, new MissionObjective(type, description)); + + foreach (var imo in player.PlayerActor.TraitsImplementing()) + imo.OnObjectiveAdded(player, newID); + + return newID; + } + + public void MarkCompleted(Player player, int objectiveID) + { + if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Completed) + return; + + objectives[objectiveID].State = ObjectiveState.Completed; + + if (objectives[objectiveID].Type == ObjectiveType.Primary) + { + var playerWon = objectives.Where(o => o.Type == ObjectiveType.Primary).All(o => o.State == ObjectiveState.Completed); + + foreach (var imo in player.PlayerActor.TraitsImplementing()) + { + imo.OnObjectiveCompleted(player, objectiveID); + + if (playerWon) + imo.OnPlayerWon(player); + } + + if (playerWon) + CheckIfGameIsOver(player); + } + } + + public void MarkFailed(Player player, int objectiveID) + { + if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Failed) + return; + + objectives[objectiveID].State = ObjectiveState.Failed; + + if (objectives[objectiveID].Type == ObjectiveType.Primary) + { + var playerLost = objectives.Where(o => o.Type == ObjectiveType.Primary).Any(o => o.State == ObjectiveState.Failed); + + foreach (var imo in player.PlayerActor.TraitsImplementing()) + { + imo.OnObjectiveFailed(player, objectiveID); + + if (playerLost) + imo.OnPlayerLost(player); + } + + if (playerLost) + CheckIfGameIsOver(player); + } + } + + void CheckIfGameIsOver(Player player) + { + var players = player.World.Players.Where(p => !p.NonCombatant); + var allies = players.Where(p => p.IsAlliedWith(player)); + + var gameOver = ((info.EarlyGameOver && !info.Cooperative && player.WinState != WinState.Undefined) || + (info.EarlyGameOver && info.Cooperative && allies.All(p => p.WinState != WinState.Undefined)) || + players.All(p => p.WinState != WinState.Undefined)); + + if (gameOver) + { + Game.RunAfterDelay(info.GameOverDelay, () => + { + player.World.EndGame(); + player.World.SetPauseState(true); + player.World.PauseStateLocked = true; + }); + } + } + + public void OnPlayerWon(Player player) + { + if (info.Cooperative) + { + WinStateCooperative = WinState.Won; + var players = player.World.Players.Where(p => !p.NonCombatant); + var allies = players.Where(p => p.IsAlliedWith(player)); + + if (allies.All(p => p.PlayerActor.Trait().WinStateCooperative == WinState.Won)) + foreach (var p in allies) + { + p.WinState = WinState.Won; + p.World.OnPlayerWinStateChanged(p); + } + } + else + { + player.WinState = WinState.Won; + player.World.OnPlayerWinStateChanged(player); + } + } + + public void OnPlayerLost(Player player) + { + if (info.Cooperative) + { + WinStateCooperative = WinState.Lost; + var players = player.World.Players.Where(p => !p.NonCombatant); + var allies = players.Where(p => p.IsAlliedWith(player)); + + if (allies.Any(p => p.PlayerActor.Trait().WinStateCooperative == WinState.Lost)) + foreach (var p in allies) + { + p.WinState = WinState.Lost; + p.World.OnPlayerWinStateChanged(p); + } + } + else + { + player.WinState = WinState.Lost; + player.World.OnPlayerWinStateChanged(player); + } + } + + public void OnObjectiveAdded(Player player, int id) {} + public void OnObjectiveCompleted(Player player, int id) {} + public void OnObjectiveFailed(Player player, int id) {} + } +} diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index d1e0dd8ff8..6fabed5517 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -2,6 +2,7 @@ Player: PlaceBuilding: TechTree: SupportPowerManager: + MissionObjectives: ConquestVictoryConditions: PowerManager: AllyRepair: diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index 1112e28208..4cbf92ba82 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -34,6 +34,7 @@ Player: BlockedAudio: NoRoom PlaceBuilding: SupportPowerManager: + MissionObjectives: ConquestVictoryConditions: PowerManager: AdviceInterval: 650 diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index 2ed6bd6b24..dfc510caf6 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -42,6 +42,7 @@ Player: RequireOwner: false PlaceBuilding: SupportPowerManager: + MissionObjectives: ConquestVictoryConditions: PowerManager: AllyRepair: diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index f44970cc41..48e5ad9742 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -26,6 +26,7 @@ Player: LowPowerSlowdown: 3 PlaceBuilding: SupportPowerManager: + MissionObjectives: ConquestVictoryConditions: PowerManager: AllyRepair: From 6e3000ab0e2aa8eaf86ecbe38818c0b57f3d962f Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Fri, 25 Jul 2014 09:25:32 +0200 Subject: [PATCH 02/13] Make Conquest- and StrategicVictoryConditions use objectives backend Note: 3rd party KotH maps will need to be modified to work correctly with this change. Previously, StrategicVictoryConditions was a supplement to ConquestVictoryConditions. After this change, each works by itself. So KotH maps will need to remove the ConquestVictoryConditions trait from the player definitions, or both victory conditions will have to be satisfied to win the game. --- OpenRA.Mods.RA/ConquestVictoryConditions.cs | 67 +++++++------- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + OpenRA.Mods.RA/Player/Extensions.cs | 23 +++++ .../Scripting/LuaScriptInterface.cs | 2 +- OpenRA.Mods.RA/StrategicVictoryConditions.cs | 90 +++++++++++++------ mods/ra/maps/koth-athena/map.yaml | 1 + mods/ra/maps/koth-crossroads/map.yaml | 1 + 7 files changed, 125 insertions(+), 60 deletions(-) create mode 100644 OpenRA.Mods.RA/Player/Extensions.cs diff --git a/OpenRA.Mods.RA/ConquestVictoryConditions.cs b/OpenRA.Mods.RA/ConquestVictoryConditions.cs index e0f5c9048e..7a5618c86f 100644 --- a/OpenRA.Mods.RA/ConquestVictoryConditions.cs +++ b/OpenRA.Mods.RA/ConquestVictoryConditions.cs @@ -13,80 +13,79 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { - public class ConquestVictoryConditionsInfo : ITraitInfo + public class ConquestVictoryConditionsInfo : ITraitInfo, Requires { - [Desc("Milliseconds")] + [Desc("Delay for the end game notification in milliseconds.")] public int NotificationDelay = 1500; - public object Create(ActorInitializer init) { return new ConquestVictoryConditions(init.world, this); } + public object Create(ActorInitializer init) { return new ConquestVictoryConditions(init.self, this); } } - public class ConquestVictoryConditions : ITick, IResolveOrder + public class ConquestVictoryConditions : ITick, IResolveOrder, INotifyObjectivesUpdated { - ConquestVictoryConditionsInfo Info; - public ConquestVictoryConditions(World world, ConquestVictoryConditionsInfo info) + readonly ConquestVictoryConditionsInfo info; + readonly MissionObjectives mo; + int objectiveID = -1; + + public ConquestVictoryConditions(Actor self, ConquestVictoryConditionsInfo cvcInfo) { - world.ObserveAfterWinOrLose = true; - Info = info; + info = cvcInfo; + mo = self.Trait(); } public void Tick(Actor self) { if (self.Owner.WinState != WinState.Undefined || self.Owner.NonCombatant) return; - var hasAnything = self.World.ActorsWithTrait() - .Any(a => a.Actor.Owner == self.Owner); + if (objectiveID < 0) + objectiveID = mo.Add(self.Owner, "Destroy all opposition!"); - if (!hasAnything && !self.Owner.NonCombatant) - Lose(self); + if (!self.Owner.NonCombatant && self.Owner.HasNoRequiredUnits()) + mo.MarkFailed(self.Owner, objectiveID); var others = self.World.Players.Where(p => !p.NonCombatant - && p != self.Owner && p.Stances[self.Owner] != Stance.Ally); + && !p.IsAlliedWith(self.Owner)); if (!others.Any()) return; if (others.All(p => p.WinState == WinState.Lost)) - Win(self); + mo.MarkCompleted(self.Owner, objectiveID); } public void ResolveOrder(Actor self, Order order) { if (order.OrderString == "Surrender") - Lose(self); + mo.MarkFailed(self.Owner, objectiveID); } - public void Lose(Actor self) + public void OnPlayerLost(Player player) { - if (self.Owner.WinState == WinState.Lost) return; - self.Owner.WinState = WinState.Lost; - self.World.OnPlayerWinStateChanged(self.Owner); + Game.Debug("{0} is defeated.".F(player.PlayerName)); - Game.Debug("{0} is defeated.".F(self.Owner.PlayerName)); - - foreach (var a in self.World.Actors.Where(a => a.Owner == self.Owner)) + foreach (var a in player.World.Actors.Where(a => a.Owner == player)) a.Kill(a); - if (self.Owner == self.World.LocalPlayer) + if (player == player.World.LocalPlayer) { - Game.RunAfterDelay(Info.NotificationDelay, () => + Game.RunAfterDelay(info.NotificationDelay, () => { - if (Game.IsCurrentWorld(self.World)) - Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Lose", self.Owner.Country.Race); + if (Game.IsCurrentWorld(player.World)) + Sound.PlayNotification(player.World.Map.Rules, player, "Speech", "Lose", player.Country.Race); }); } } - public void Win(Actor self) + public void OnPlayerWon(Player player) { - if (self.Owner.WinState == WinState.Won) return; - self.Owner.WinState = WinState.Won; - self.World.OnPlayerWinStateChanged(self.Owner); + Game.Debug("{0} is victorious.".F(player.PlayerName)); - Game.Debug("{0} is victorious.".F(self.Owner.PlayerName)); - - if (self.Owner == self.World.LocalPlayer) - Game.RunAfterDelay(Info.NotificationDelay, () => Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Win", self.Owner.Country.Race)); + if (player == player.World.LocalPlayer) + Game.RunAfterDelay(info.NotificationDelay, () => Sound.PlayNotification(player.World.Map.Rules, player, "Speech", "Win", player.Country.Race)); } + + public void OnObjectiveAdded(Player player, int id) {} + public void OnObjectiveCompleted(Player player, int id) {} + public void OnObjectiveFailed(Player player, int id) {} } [Desc("Tag trait for things that must be destroyed for a short game to end.")] diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index bc21912282..132c5cebc8 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -552,6 +552,7 @@ + diff --git a/OpenRA.Mods.RA/Player/Extensions.cs b/OpenRA.Mods.RA/Player/Extensions.cs new file mode 100644 index 0000000000..840ef08ec7 --- /dev/null +++ b/OpenRA.Mods.RA/Player/Extensions.cs @@ -0,0 +1,23 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Linq; +using OpenRA.Mods.RA; + +namespace OpenRA +{ + public static class Extensions + { + public static bool HasNoRequiredUnits(this Player player) + { + return player.World.ActorsWithTrait().All(p => p.Actor.Owner != player); + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs index 84cf7020fc..fd304d4e83 100644 --- a/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs +++ b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs @@ -319,7 +319,7 @@ namespace OpenRA.Mods.RA.Scripting [LuaGlobal] public bool RequiredUnitsAreDestroyed(Player player) { - return world.ActorsWithTrait().All(p => p.Actor.Owner != player); + return player.HasNoRequiredUnits(); } [LuaGlobal] diff --git a/OpenRA.Mods.RA/StrategicVictoryConditions.cs b/OpenRA.Mods.RA/StrategicVictoryConditions.cs index 86c1344741..9e079990bb 100644 --- a/OpenRA.Mods.RA/StrategicVictoryConditions.cs +++ b/OpenRA.Mods.RA/StrategicVictoryConditions.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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, @@ -17,41 +17,68 @@ namespace OpenRA.Mods.RA public class StrategicPointInfo : TraitInfo {} public class StrategicPoint {} - public class StrategicVictoryConditionsInfo : ITraitInfo, Requires + public class StrategicVictoryConditionsInfo : ITraitInfo, Requires { + [Desc("Amount of time (in game ticks) that the player has to hold all the strategic points.")] public readonly int TicksToHold = 25 * 60 * 5; // ~5 minutes + + [Desc("Should the timer reset when the player loses hold of a strategic point.")] public readonly bool ResetOnHoldLost = true; + + [Desc("Percentage of strategic points the player has to hold to win.")] public readonly float RatioRequired = 0.5f; // 50% required of all koth locations + [Desc("Delay for the end game notification in milliseconds.")] + public int NotificationDelay = 1500; + public object Create(ActorInitializer init) { return new StrategicVictoryConditions(init.self, this); } } - public class StrategicVictoryConditions : ITick, ISync + public class StrategicVictoryConditions : ITick, ISync, INotifyObjectivesUpdated { - Actor self; - StrategicVictoryConditionsInfo info; + readonly StrategicVictoryConditionsInfo info; - [Sync] public int TicksLeft = 0; + [Sync] public int TicksLeft; + readonly Player player; + readonly MissionObjectives mo; + int objectiveID = -1; - public StrategicVictoryConditions(Actor self, StrategicVictoryConditionsInfo info) + public StrategicVictoryConditions(Actor self, StrategicVictoryConditionsInfo svcInfo) { - this.self = self; - this.info = info; + info = svcInfo; + TicksLeft = info.TicksToHold; + player = self.Owner; + mo = self.Trait(); } public IEnumerable> AllPoints { - get { return self.World.ActorsWithTrait(); } + get { return player.World.ActorsWithTrait(); } } public int Total { get { return AllPoints.Count(); } } - int Owned { get { return AllPoints.Count( a => WorldUtils.AreMutualAllies( self.Owner, a.Actor.Owner )); } } + int Owned { get { return AllPoints.Count(a => WorldUtils.AreMutualAllies(player, a.Actor.Owner)); } } public bool Holding { get { return Owned >= info.RatioRequired * Total; } } public void Tick(Actor self) { - if (self.Owner.WinState != WinState.Undefined || self.Owner.NonCombatant) return; + if (player.WinState != WinState.Undefined || player.NonCombatant) return; + + if (objectiveID < 0) + objectiveID = mo.Add(player, "Hold all the strategic positions for a specified time!"); + + if (!self.Owner.NonCombatant && self.Owner.HasNoRequiredUnits()) + mo.MarkFailed(self.Owner, objectiveID); + + var others = self.World.Players.Where(p => !p.NonCombatant + && !p.IsAlliedWith(self.Owner)); + + if (others.All(p => p.WinState == WinState.Lost)) + mo.MarkCompleted(player, objectiveID); + + if (others.Any(p => p.WinState == WinState.Won)) + mo.MarkFailed(player, objectiveID); // See if any of the conditions are met to increase the count if (Total > 0) @@ -59,10 +86,8 @@ namespace OpenRA.Mods.RA if (Holding) { // Hah! We met ths critical owned condition - if (TicksLeft == 0) - TicksLeft = info.TicksToHold; // first tick -- this is crap. - else if (--TicksLeft == 0) - Won(); + if (--TicksLeft == 0) + mo.MarkCompleted(player, objectiveID); } else if (TicksLeft != 0) if (info.ResetOnHoldLost) @@ -70,18 +95,33 @@ namespace OpenRA.Mods.RA } } - void Won() + public void OnPlayerLost(Player player) { - // Player has won - foreach (var p in self.World.Players) - { - var cvc = p.PlayerActor.Trait(); + Game.Debug("{0} is defeated.".F(player.PlayerName)); - if (p.WinState == WinState.Undefined && WorldUtils.AreMutualAllies(self.Owner, p)) - cvc.Win(p.PlayerActor); - else if (p.WinState == WinState.Undefined) - cvc.Lose(p.PlayerActor); + foreach (var a in player.World.Actors.Where(a => a.Owner == player)) + a.Kill(a); + + if (player == player.World.LocalPlayer) + { + Game.RunAfterDelay(info.NotificationDelay, () => + { + if (Game.IsCurrentWorld(player.World)) + Sound.PlayNotification(player.World.Map.Rules, player, "Speech", "Lose", player.Country.Race); + }); } } + + public void OnPlayerWon(Player player) + { + Game.Debug("{0} is victorious.".F(player.PlayerName)); + + if (player == player.World.LocalPlayer) + Game.RunAfterDelay(info.NotificationDelay, () => Sound.PlayNotification(player.World.Map.Rules, player, "Speech", "Win", player.Country.Race)); + } + + public void OnObjectiveAdded(Player player, int id) {} + public void OnObjectiveCompleted(Player player, int id) {} + public void OnObjectiveFailed(Player player, int id) {} } } diff --git a/mods/ra/maps/koth-athena/map.yaml b/mods/ra/maps/koth-athena/map.yaml index 7dd203f32a..31441ff12b 100644 --- a/mods/ra/maps/koth-athena/map.yaml +++ b/mods/ra/maps/koth-athena/map.yaml @@ -2113,6 +2113,7 @@ Rules: -Selectable: -TargetableBuilding: Player: + -ConquestVictoryConditions: StrategicVictoryConditions: TicksToHold: 4500 ResetOnHoldLost: true diff --git a/mods/ra/maps/koth-crossroads/map.yaml b/mods/ra/maps/koth-crossroads/map.yaml index b5e2a96984..9e70dd1f57 100644 --- a/mods/ra/maps/koth-crossroads/map.yaml +++ b/mods/ra/maps/koth-crossroads/map.yaml @@ -264,6 +264,7 @@ Rules: -Selectable: -TargetableBuilding: Player: + -ConquestVictoryConditions: StrategicVictoryConditions: TicksToHold: 3000 ResetOnHoldLost: true From a448ba29a85321c279720c19d8fd22aa8bfc8659 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Wed, 11 Jun 2014 13:41:20 +0200 Subject: [PATCH 03/13] Move trigger lists into a dictionary for easier access and enumeration --- .../Scripting/Global/TriggerGlobal.cs | 22 +++++++-- OpenRA.Mods.RA/Scripting/ScriptTriggers.cs | 47 ++++++++++--------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index 9ed2f74738..a6faa0f59c 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -56,21 +56,21 @@ namespace OpenRA.Mods.RA.Scripting "The callback function will be called as func(Actor self).")] public void OnIdle(Actor a, LuaFunction func) { - GetScriptTriggers(a).RegisterIdleCallback(func, context); + GetScriptTriggers(a).RegisterCallback(Trigger.OnIdle, func, context); } [Desc("Call a function when the actor is damaged. The callback " + "function will be called as func(Actor self, Actor attacker).")] public void OnDamaged(Actor a, LuaFunction func) { - GetScriptTriggers(a).RegisterDamagedCallback(func, context); + GetScriptTriggers(a).RegisterCallback(Trigger.OnDamaged, func, context); } [Desc("Call a function when the actor is killed. The callback " + "function will be called as func(Actor self, Actor killer).")] public void OnKilled(Actor a, LuaFunction func) { - GetScriptTriggers(a).RegisterKilledCallback(func, context); + GetScriptTriggers(a).RegisterCallback(Trigger.OnKilled, func, context); } [Desc("Call a function when all of the actors in a group are killed. The callback " + @@ -106,7 +106,21 @@ namespace OpenRA.Mods.RA.Scripting "The callback function will be called as func(Actor producer, Actor produced).")] public void OnProduction(Actor a, LuaFunction func) { - GetScriptTriggers(a).RegisterProductionCallback(func, context); + GetScriptTriggers(a).RegisterCallback(Trigger.OnProduction, func, context); + } + + [Desc("Removes all triggers from this actor")] + public void ClearAll(Actor a) + { + GetScriptTriggers(a).ClearAll(); + } + + [Desc("Removes the specified trigger from this actor")] + public void Clear(Actor a, string triggerName) + { + var trigger = (Trigger)Enum.Parse(typeof(Trigger), triggerName); + + GetScriptTriggers(a).Clear(trigger); } } } diff --git a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs index d68e9c2112..0961e31bf6 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs @@ -18,6 +18,8 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Scripting { + public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction }; + [Desc("Allows map scripts to attach triggers to this actor via the Triggers global.")] public class ScriptTriggersInfo : TraitInfo { } @@ -25,34 +27,22 @@ namespace OpenRA.Mods.RA.Scripting { public event Action OnKilledInternal = _ => {}; - List> onIdle = new List>(); - List> onDamaged = new List>(); - List> onKilled = new List>(); - List> onProduction = new List>(); + public Dictionary>> Triggers = new Dictionary>>(); - public void RegisterIdleCallback(LuaFunction func, ScriptContext context) + public ScriptTriggers() { - onIdle.Add(Pair.New((LuaFunction)func.CopyReference(), context)); + foreach (var t in Enum.GetValues(typeof(Trigger)).Cast()) + Triggers.Add(t, new List>()); } - public void RegisterDamagedCallback(LuaFunction func, ScriptContext context) + public void RegisterCallback(Trigger trigger, LuaFunction func, ScriptContext context) { - onDamaged.Add(Pair.New((LuaFunction)func.CopyReference(), context)); - } - - public void RegisterKilledCallback(LuaFunction func, ScriptContext context) - { - onKilled.Add(Pair.New((LuaFunction)func.CopyReference(), context)); - } - - public void RegisterProductionCallback(LuaFunction func, ScriptContext context) - { - onProduction.Add(Pair.New((LuaFunction)func.CopyReference(), context)); + Triggers[trigger].Add(Pair.New((LuaFunction)func.CopyReference(), context)); } public void TickIdle(Actor self) { - foreach (var f in onIdle) + foreach (var f in Triggers[Trigger.OnIdle]) { var a = self.ToLuaValue(f.Second); f.First.Call(a).Dispose(); @@ -62,7 +52,7 @@ namespace OpenRA.Mods.RA.Scripting public void Damaged(Actor self, AttackInfo e) { - foreach (var f in onDamaged) + foreach (var f in Triggers[Trigger.OnDamaged]) { var a = self.ToLuaValue(f.Second); var b = e.Attacker.ToLuaValue(f.Second); @@ -75,7 +65,7 @@ namespace OpenRA.Mods.RA.Scripting public void Killed(Actor self, AttackInfo e) { // Run lua callbacks - foreach (var f in onKilled) + foreach (var f in Triggers[Trigger.OnKilled]) { var a = self.ToLuaValue(f.Second); var b = e.Attacker.ToLuaValue(f.Second); @@ -90,7 +80,7 @@ namespace OpenRA.Mods.RA.Scripting public void UnitProduced(Actor self, Actor other, CPos exit) { - foreach (var f in onProduction) + foreach (var f in Triggers[Trigger.OnProduction]) { var a = self.ToLuaValue(f.Second); var b = other.ToLuaValue(f.Second); @@ -100,9 +90,20 @@ namespace OpenRA.Mods.RA.Scripting } } + public void Clear(Trigger trigger) + { + Triggers[trigger].Clear(); + } + + public void ClearAll() + { + foreach (var trigger in Triggers) + trigger.Value.Clear(); + } + public void Dispose() { - var pairs = new[] { onIdle, onDamaged, onKilled, onProduction }; + var pairs = Triggers.Values; pairs.SelectMany(l => l).Select(p => p.First).Do(f => f.Dispose()); pairs.Do(l => l.Clear()); } From cca6646927086db752fa81339503fc486768405c Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 7 Jul 2014 12:12:26 +0200 Subject: [PATCH 04/13] Add mission objectives to Lua interface --- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + .../Scripting/Global/TriggerGlobal.cs | 35 ++++++++++ .../Properties/MissionObjectiveProperties.cs | 70 +++++++++++++++++++ OpenRA.Mods.RA/Scripting/ScriptTriggers.cs | 60 +++++++++++++++- mods/cnc/rules/player.yaml | 1 + mods/d2k/rules/player.yaml | 1 + mods/ra/rules/player.yaml | 1 + mods/ts/rules/player.yaml | 1 + 8 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 132c5cebc8..8d182014a0 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -485,6 +485,7 @@ + diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index a6faa0f59c..a2dca58344 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -109,6 +109,41 @@ namespace OpenRA.Mods.RA.Scripting GetScriptTriggers(a).RegisterCallback(Trigger.OnProduction, func, context); } + [Desc("Call a function when this player completes all primary objectives. " + + "The callback function will be called as func(Player player).")] + public void OnPlayerWon(Player player, LuaFunction func) + { + GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnPlayerWon, func, context); + } + + [Desc("Call a function when this player fails any primary objective. " + + "The callback function will be called as func(Player player).")] + public void OnPlayerLost(Player player, LuaFunction func) + { + GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnPlayerLost, func, context); + } + + [Desc("Call a function when this player is assigned a new objective. " + + "The callback function will be called as func(Player player, int objectiveID).")] + public void OnObjectiveAdded(Player player, LuaFunction func) + { + GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnObjectiveAdded, func, context); + } + + [Desc("Call a function when this player completes an objective " + + "The callback function will be called as func(Player player, int objectiveID).")] + public void OnObjectiveCompleted(Player player, LuaFunction func) + { + GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnObjectiveCompleted, func, context); + } + + [Desc("Call a function when this player fails an objective " + + "The callback function will be called as func(Player player, int objectiveID).")] + public void OnObjectiveFailed(Player player, LuaFunction func) + { + GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnObjectiveFailed, func, context); + } + [Desc("Removes all triggers from this actor")] public void ClearAll(Actor a) { diff --git a/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs new file mode 100644 index 0000000000..b11a6b6af4 --- /dev/null +++ b/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs @@ -0,0 +1,70 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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 OpenRA.Traits; +using OpenRA.Scripting; +using OpenRA.Mods.RA; + +namespace OpenRA.Mods.RA.Scripting +{ + [ScriptPropertyGroup("MissionObjectives")] + public class MissionObjectiveProperties : ScriptPlayerProperties + { + readonly MissionObjectives mo; + + public MissionObjectiveProperties(Player player) + : base(player) + { + mo = player.PlayerActor.Trait(); + } + + [ScriptActorPropertyActivity] + [Desc("Add a primary mission objective for this player. The function returns the " + + "ID of the newly created objective, so that it can be referred to later.")] + public int AddPrimaryObjective(string description) + { + return mo.Add(player, description, ObjectiveType.Primary); + } + + [ScriptActorPropertyActivity] + [Desc("Add a secondary mission objective for this player. The function returns the " + + "ID of the newly created objective, so that it can be referred to later.")] + public int AddSecondaryObjective(string description) + { + return mo.Add(player, description, ObjectiveType.Secondary); + } + + [ScriptActorPropertyActivity] + [Desc("Mark an objective as completed. This needs the objective ID returned " + + "by AddObjective as argument. When the player has completed all primary " + + "objectives, (s)he has won the game.")] + public void MarkCompletedObjective(int id) + { + mo.MarkCompleted(player, id); + } + + [ScriptActorPropertyActivity] + [Desc("Mark an objective as failed. This needs the objective ID returned " + + "by AddObjective as argument. Secondary objectives do not have any " + + "influence whatsoever on the outcome of the game.")] + public void MarkFailedObjective(int id) + { + mo.MarkFailed(player, id); + } + + [ScriptActorPropertyActivity] + [Desc("Returns true if 'player' has lost all units/actors that have the 'MustBeDestroyed' trait.")] + public bool HasNoRequiredUnits() + { + return player.HasNoRequiredUnits(); + } + } +} diff --git a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs index 0961e31bf6..9c2a8e597b 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs @@ -18,12 +18,12 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Scripting { - public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction }; + public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction, OnPlayerWon, OnPlayerLost, OnObjectiveAdded, OnObjectiveCompleted, OnObjectiveFailed }; [Desc("Allows map scripts to attach triggers to this actor via the Triggers global.")] public class ScriptTriggersInfo : TraitInfo { } - public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, IDisposable + public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyObjectivesUpdated, IDisposable { public event Action OnKilledInternal = _ => {}; @@ -90,6 +90,62 @@ namespace OpenRA.Mods.RA.Scripting } } + public void OnPlayerWon(Player player) + { + foreach (var f in Triggers[Trigger.OnPlayerWon]) + { + var a = player.ToLuaValue(f.Second); + f.First.Call(a).Dispose(); + a.Dispose(); + } + } + + public void OnPlayerLost(Player player) + { + foreach (var f in Triggers[Trigger.OnPlayerLost]) + { + var a = player.ToLuaValue(f.Second); + f.First.Call(a).Dispose(); + a.Dispose(); + } + } + + public void OnObjectiveAdded(Player player, int id) + { + foreach (var f in Triggers[Trigger.OnObjectiveAdded]) + { + var a = player.ToLuaValue(f.Second); + var b = id.ToLuaValue(f.Second); + f.First.Call(a, b).Dispose(); + a.Dispose(); + b.Dispose(); + } + } + + public void OnObjectiveCompleted(Player player, int id) + { + foreach (var f in Triggers[Trigger.OnObjectiveCompleted]) + { + var a = player.ToLuaValue(f.Second); + var b = id.ToLuaValue(f.Second); + f.First.Call(a, b).Dispose(); + a.Dispose(); + b.Dispose(); + } + } + + public void OnObjectiveFailed(Player player, int id) + { + foreach (var f in Triggers[Trigger.OnObjectiveFailed]) + { + var a = player.ToLuaValue(f.Second); + var b = id.ToLuaValue(f.Second); + f.First.Call(a, b).Dispose(); + a.Dispose(); + b.Dispose(); + } + } + public void Clear(Trigger trigger) { Triggers[trigger].Clear(); diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index 6fabed5517..f177d84ce9 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -2,6 +2,7 @@ Player: PlaceBuilding: TechTree: SupportPowerManager: + ScriptTriggers: MissionObjectives: ConquestVictoryConditions: PowerManager: diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index 4cbf92ba82..e55955cda7 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -34,6 +34,7 @@ Player: BlockedAudio: NoRoom PlaceBuilding: SupportPowerManager: + ScriptTriggers: MissionObjectives: ConquestVictoryConditions: PowerManager: diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index dfc510caf6..fdf86884bc 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -42,6 +42,7 @@ Player: RequireOwner: false PlaceBuilding: SupportPowerManager: + ScriptTriggers: MissionObjectives: ConquestVictoryConditions: PowerManager: diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index 48e5ad9742..80a376ccd8 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -26,6 +26,7 @@ Player: LowPowerSlowdown: 3 PlaceBuilding: SupportPowerManager: + ScriptTriggers: MissionObjectives: ConquestVictoryConditions: PowerManager: From 7546e8237efabae240500c72d40c5a148181d3ec Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 14 Jul 2014 16:40:21 +0200 Subject: [PATCH 05/13] Make CheckboxWidget honour GetText() --- OpenRA.Game/Widgets/CheckboxWidget.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OpenRA.Game/Widgets/CheckboxWidget.cs b/OpenRA.Game/Widgets/CheckboxWidget.cs index ad7e7c251b..29a2726ac4 100644 --- a/OpenRA.Game/Widgets/CheckboxWidget.cs +++ b/OpenRA.Game/Widgets/CheckboxWidget.cs @@ -50,7 +50,8 @@ namespace OpenRA.Widgets var colordisabled = GetColorDisabled(); var contrast = GetContrastColor(); var rect = RenderBounds; - var textSize = font.Measure(Text); + var text = GetText(); + var textSize = font.Measure(text); var check = new Rectangle(rect.Location, new Size(Bounds.Height, Bounds.Height)); var state = disabled ? "checkbox-disabled" : highlighted ? "checkbox-highlighted" : @@ -62,10 +63,10 @@ namespace OpenRA.Widgets var position = new float2(rect.Left + rect.Height * 1.5f, RenderOrigin.Y - BaseLine + (Bounds.Height - textSize.Y)/2); if (Contrast) - font.DrawTextWithContrast(Text, position, + font.DrawTextWithContrast(text, position, disabled ? colordisabled : color, contrast, 2); else - font.DrawText(Text, position, + font.DrawText(text, position, disabled ? colordisabled : color); if (IsChecked() || (Depressed && HasPressedState && !disabled)) From 2c22e5099f63d3ad9d82c69a56c3fac34e4e02b8 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Sun, 27 Jul 2014 12:55:38 +0200 Subject: [PATCH 06/13] Copy checkbox markings from TD to the other mods --- mods/d2k/chrome.yaml | 5 ++++- mods/d2k/uibits/buttons.png | Bin 20161 -> 19696 bytes mods/ra/chrome.yaml | 5 ++++- mods/ra/uibits/buttons.png | Bin 21057 -> 22113 bytes mods/ts/chrome.yaml | 5 ++++- mods/ts/uibits/buttons.png | Bin 10005 -> 10076 bytes 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mods/d2k/chrome.yaml b/mods/d2k/chrome.yaml index cf54d9356e..119686643f 100644 --- a/mods/d2k/chrome.yaml +++ b/mods/d2k/chrome.yaml @@ -521,7 +521,10 @@ checkbox: dialog.png corner-br: 767,127,1,1 checkbox-bits: buttons.png - checked: 0,140,16,16 + checked: 0,157,16,16 + checked-disabled: 0,173,16,16 + crossed: 16,157,16,16 + crossed-disabled: 16,173,16,16 checkbox-hover: dialog.png background: 641,129,126,126 diff --git a/mods/d2k/uibits/buttons.png b/mods/d2k/uibits/buttons.png index b2b4085580be81e9814d7bba9ddb4fbd42f80ac5..93f928ee986bf80aa59392b42b0610405ce13a3a 100644 GIT binary patch literal 19696 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 W9v+ZIJrAe^aE2wu*0jkOi~kow!;1a@ diff --git a/mods/ra/chrome.yaml b/mods/ra/chrome.yaml index 02cb7d045e..db547c0ab1 100644 --- a/mods/ra/chrome.yaml +++ b/mods/ra/chrome.yaml @@ -427,7 +427,10 @@ checkbox: dialog.png corner-br: 767,127,1,1 checkbox-bits: buttons.png - checked: 0,140,16,16 + checked: 0,157,16,16 + checked-disabled: 0,173,16,16 + crossed: 16,157,16,16 + crossed-disabled: 16,173,16,16 checkbox-hover: dialog.png background: 641,129,126,126 diff --git a/mods/ra/uibits/buttons.png b/mods/ra/uibits/buttons.png index 9e1491a0493e769ae4aaee546862efcdfc050bdf..16b9674eb6124b19afa210d94d037400d7743bfa 100644 GIT binary patch literal 22113 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 literal 10005 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 HH+%mdkVk&n From 8cec848a0f8894151f1d7890c70e2f21bdf8ce0c Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Sun, 27 Jul 2014 13:25:58 +0200 Subject: [PATCH 07/13] Add a mission objectives GUI panel --- OpenRA.Game/Traits/TraitsInterfaces.cs | 2 +- OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj | 2 - .../Widgets/Logic/CncIngameMenuLogic.cs | 131 ----------- .../Widgets/Logic/IngameChromeLogic.cs | 81 +------ OpenRA.Mods.RA/ConquestVictoryConditions.cs | 15 -- OpenRA.Mods.RA/MenuPaletteEffect.cs | 5 +- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 4 + OpenRA.Mods.RA/Player/MissionObjectives.cs | 20 ++ .../Widgets/Logic/DebugMenuLogic.cs | 17 +- .../Widgets/Logic/DiplomacyLogic.cs | 12 +- .../Logic/Ingame/GameInfoBriefingLogic.cs | 38 +++ .../Widgets/Logic/Ingame/GameInfoLogic.cs | 109 +++++++++ .../Logic/Ingame/GameInfoObjectivesLogic.cs | 72 ++++++ .../Logic/Ingame/GameInfoStatsLogic.cs | 50 ++-- .../Widgets/Logic/IngameMenuLogic.cs | 100 ++++---- .../Widgets/Logic/ObserverStatsLogic.cs | 12 +- .../Widgets/Logic/OrderButtonsChromeLogic.cs | 55 +++-- mods/cnc/chrome/ingame-debug.yaml | 220 +++++++++--------- mods/cnc/chrome/ingame-info.yaml | 55 +++++ mods/cnc/chrome/ingame-infobriefing.yaml | 32 +++ mods/cnc/chrome/ingame-infoobjectives.yaml | 40 ++++ mods/cnc/chrome/ingame-infostats.yaml | 108 +++++++++ mods/cnc/chrome/ingame-menu.yaml | 32 +-- mods/cnc/chrome/objectives.yaml | 120 ---------- mods/cnc/mod.yaml | 7 +- mods/cnc/rules/world.yaml | 5 +- mods/d2k/chrome/ingame-menu.yaml | 61 +++++ mods/d2k/chrome/ingame-observer.yaml | 6 +- mods/d2k/chrome/ingame-player.yaml | 19 +- mods/d2k/chrome/ingame.yaml | 3 +- mods/d2k/mod.yaml | 9 +- mods/d2k/rules/world.yaml | 2 + mods/ra/chrome/ingame-debug.yaml | 179 +++++++------- mods/ra/chrome/ingame-info.yaml | 64 +++++ mods/ra/chrome/ingame-infobriefing.yaml | 32 +++ mods/ra/chrome/ingame-infoobjectives.yaml | 40 ++++ mods/ra/chrome/ingame-infostats.yaml | 108 +++++++++ mods/ra/chrome/ingame-menu.yaml | 118 ++++++---- mods/ra/chrome/ingame-objectives.yaml | 98 -------- mods/ra/chrome/ingame-observer.yaml | 4 +- mods/ra/chrome/ingame-player.yaml | 14 -- mods/ra/mod.yaml | 8 +- mods/ra/rules/world.yaml | 2 + mods/ts/chrome/ingame-player.yaml | 18 +- mods/ts/chrome/ingame.yaml | 4 +- mods/ts/mod.yaml | 5 +- 46 files changed, 1252 insertions(+), 886 deletions(-) delete mode 100644 OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs create mode 100644 OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs create mode 100644 OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoLogic.cs create mode 100644 OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoObjectivesLogic.cs rename OpenRA.Mods.Cnc/Widgets/Logic/CncConquestObjectivesLogic.cs => OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoStatsLogic.cs (53%) create mode 100644 mods/cnc/chrome/ingame-info.yaml create mode 100644 mods/cnc/chrome/ingame-infobriefing.yaml create mode 100644 mods/cnc/chrome/ingame-infoobjectives.yaml create mode 100644 mods/cnc/chrome/ingame-infostats.yaml delete mode 100644 mods/cnc/chrome/objectives.yaml create mode 100644 mods/d2k/chrome/ingame-menu.yaml create mode 100644 mods/ra/chrome/ingame-info.yaml create mode 100644 mods/ra/chrome/ingame-infobriefing.yaml create mode 100644 mods/ra/chrome/ingame-infoobjectives.yaml create mode 100644 mods/ra/chrome/ingame-infostats.yaml delete mode 100644 mods/ra/chrome/ingame-objectives.yaml diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index f5dfce3daf..e8fb764998 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -271,7 +271,7 @@ namespace OpenRA.Traits public interface ILintPass { void Run(Action emitError, Action emitWarning, Map map); } - public interface IObjectivesPanel { string ObjectivesPanel { get; } } + public interface IObjectivesPanel { string PanelName { get; } } public interface INotifyObjectivesUpdated { diff --git a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj index 9f86250d9f..de31845598 100644 --- a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj +++ b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj @@ -75,8 +75,6 @@ - - diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs deleted file mode 100644 index 3826fff390..0000000000 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameMenuLogic.cs +++ /dev/null @@ -1,131 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 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.Graphics; -using OpenRA.Mods.RA; -using OpenRA.Mods.RA.Widgets; -using OpenRA.Traits; -using OpenRA.Widgets; - -namespace OpenRA.Mods.Cnc.Widgets.Logic -{ - public class CncIngameMenuLogic - { - Widget menu; - - enum PanelType { Objectives, Debug } - - [ObjectCreator.UseCtor] - public CncIngameMenuLogic(Widget widget, World world, Action onExit, WorldRenderer worldRenderer) - { - var resumeDisabled = false; - menu = widget.Get("INGAME_MENU"); - var mpe = world.WorldActor.Trait(); - mpe.Fade(MenuPaletteEffect.EffectType.Desaturated); - - menu.Get("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version; - - var hideButtons = false; - menu.Get("MENU_BUTTONS").IsVisible = () => !hideButtons; - - // TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions - Action onQuit = () => - { - Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", null); - resumeDisabled = true; - Game.RunAfterDelay(1200, () => mpe.Fade(MenuPaletteEffect.EffectType.Black)); - Game.RunAfterDelay(1200 + 40 * mpe.Info.FadeLength, () => - { - Game.Disconnect(); - Ui.ResetAll(); - Game.LoadShellMap(); - }); - }; - - Action doNothing = () => { }; - - menu.Get("QUIT_BUTTON").OnClick = () => - ConfirmationDialogs.PromptConfirmAction("Abort Mission", "Leave this game and return to the menu?", onQuit, doNothing); - - Action onSurrender = () => world.IssueOrder(new Order("Surrender", world.LocalPlayer.PlayerActor, false)); - var surrenderButton = menu.Get("SURRENDER_BUTTON"); - surrenderButton.IsDisabled = () => (world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined); - surrenderButton.OnClick = () => - ConfirmationDialogs.PromptConfirmAction("Surrender", "Are you sure you want to surrender?", onSurrender, doNothing); - - menu.Get("MUSIC_BUTTON").OnClick = () => - { - hideButtons = true; - Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs() - { - { "onExit", () => hideButtons = false }, - { "world", world } - }); - }; - - menu.Get("SETTINGS_BUTTON").OnClick = () => - { - hideButtons = true; - Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() - { - { "world", world }, - { "worldRenderer", worldRenderer }, - { "onExit", () => hideButtons = false }, - }); - }; - - var resumeButton = menu.Get("RESUME_BUTTON"); - resumeButton.IsDisabled = () => resumeDisabled; - resumeButton.OnClick = () => - { - Ui.CloseWindow(); - Ui.Root.RemoveChild(menu); - world.WorldActor.Trait().Fade(MenuPaletteEffect.EffectType.None); - onExit(); - }; - - // Menu panels - ordered from lowest to highest priority - var panelParent = Game.OpenWindow(world, "INGAME_MENU_PANEL"); - var panelType = PanelType.Objectives; - var visibleButtons = 0; - - // Debug / Cheats panel - var debugButton = panelParent.Get("DEBUG_BUTTON"); - debugButton.OnClick = () => panelType = PanelType.Debug; - debugButton.IsHighlighted = () => panelType == PanelType.Debug; - - if (world.LocalPlayer != null && world.LobbyInfo.GlobalSettings.AllowCheats) - { - panelType = PanelType.Debug; - visibleButtons++; - var debugPanel = Game.LoadWidget(world, "DEBUG_PANEL", panelParent, new WidgetArgs() { { "onExit", doNothing }, { "transient", true } }); - debugPanel.IsVisible = () => panelType == PanelType.Debug; - debugButton.IsVisible = () => visibleButtons > 1; - } - - // Mission objectives - var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); - var objectivesButton = panelParent.Get("OBJECTIVES_BUTTON"); - objectivesButton.OnClick = () => panelType = PanelType.Objectives; - objectivesButton.IsHighlighted = () => panelType == PanelType.Objectives; - - if (iop != null && iop.ObjectivesPanel != null) - { - panelType = PanelType.Objectives; - visibleButtons++; - var objectivesPanel = Game.LoadWidget(world, iop.ObjectivesPanel, panelParent, new WidgetArgs()); - objectivesPanel.IsVisible = () => panelType == PanelType.Objectives; - objectivesButton.IsVisible = () => visibleButtons > 1; - } - } - } -} diff --git a/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs b/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs index db55cffc9d..a7f254bc0a 100644 --- a/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs +++ b/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs @@ -13,6 +13,8 @@ using System.Linq; using OpenRA.Mods.RA; using OpenRA.Mods.RA.Buildings; using OpenRA.Mods.RA.Widgets; +using OpenRA.Mods.RA.Widgets.Logic; +using OpenRA.Mods.D2k.Widgets; using OpenRA.Traits; using OpenRA.Widgets; @@ -40,54 +42,12 @@ namespace OpenRA.Mods.D2k.Widgets.Logic void InitRootWidgets() { - var cachedPause = false; - Widget optionsBG = null; - optionsBG = Game.LoadWidget(world, "INGAME_OPTIONS_BG", Ui.Root, new WidgetArgs - { - { "transient", false }, - { "onExit", () => - { - optionsBG.Visible = false; - - if (world.LobbyInfo.IsSinglePlayer) - world.SetPauseState(cachedPause); - } - } - }); - - optionsBG.Visible = false; - - gameRoot.Get("INGAME_OPTIONS_BUTTON").OnClick = () => - { - optionsBG.Visible ^= true; - if (optionsBG.Visible) - { - cachedPause = world.PredictedPaused; - - if (world.LobbyInfo.IsSinglePlayer) - world.SetPauseState(true); - } - else - world.SetPauseState(cachedPause); - }; - Game.LoadWidget(world, "CHAT_PANEL", gameRoot, new WidgetArgs()); } void InitObserverWidgets() { - var observerWidgets = Game.LoadWidget(world, "OBSERVER_WIDGETS", playerRoot, new WidgetArgs()); - - Widget observerstats = null; - observerstats = Game.LoadWidget(world, "INGAME_OBSERVERSTATS_BG", observerWidgets, new WidgetArgs - { - { "transient", false }, - { "onExit", () => observerstats.Visible = false } - }); - observerstats.Visible = false; - - var statsButton = observerWidgets.Get("OBSERVER_STATS_BUTTON"); - statsButton.OnClick = () => observerstats.Visible ^= true; + Game.LoadWidget(world, "OBSERVER_WIDGETS", playerRoot, new WidgetArgs()); } enum RadarBinState { Closed, BinAnimating, RadarAnimating, Open }; @@ -95,41 +55,6 @@ namespace OpenRA.Mods.D2k.Widgets.Logic { var playerWidgets = Game.LoadWidget(world, "PLAYER_WIDGETS", playerRoot, new WidgetArgs()); - Widget diplomacy = null; - diplomacy = Game.LoadWidget(world, "INGAME_DIPLOMACY_BG", playerWidgets, new WidgetArgs - { - { "transient", false }, - { "onExit", () => diplomacy.Visible = false } - }); - diplomacy.Visible = false; - - var diplomacyButton = playerWidgets.Get("INGAME_DIPLOMACY_BUTTON"); - diplomacyButton.OnClick = () => diplomacy.Visible ^= true; - var validPlayers = 0; - validPlayers = world.Players.Where(a => a != world.LocalPlayer && !a.NonCombatant).Count(); - diplomacyButton.IsVisible = () => validPlayers > 0; - - Widget cheats = null; - cheats = Game.LoadWidget(world, "INGAME_DEBUG_BG", playerWidgets, new WidgetArgs - { - { "transient", false }, - { "onExit", () => cheats.Visible = false } - }); - cheats.Visible = false; - - var cheatsButton = playerWidgets.Get("INGAME_DEBUG_BUTTON"); - cheatsButton.OnClick = () => cheats.Visible ^= true; - cheatsButton.IsVisible = () => world.LobbyInfo.GlobalSettings.AllowCheats; - - var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); - if (iop != null && iop.ObjectivesPanel != null) - { - var objectivesButton = playerWidgets.Get("OBJECTIVES_BUTTON"); - var objectivesWidget = Game.LoadWidget(world, iop.ObjectivesPanel, playerWidgets, new WidgetArgs()); - objectivesButton.Visible = true; - objectivesButton.OnClick += () => objectivesWidget.Visible ^= true; - } - var radarActive = false; var binState = RadarBinState.Closed; var radarBin = playerWidgets.Get("INGAME_RADAR_BIN"); diff --git a/OpenRA.Mods.RA/ConquestVictoryConditions.cs b/OpenRA.Mods.RA/ConquestVictoryConditions.cs index 7a5618c86f..f4e277d44c 100644 --- a/OpenRA.Mods.RA/ConquestVictoryConditions.cs +++ b/OpenRA.Mods.RA/ConquestVictoryConditions.cs @@ -91,19 +91,4 @@ namespace OpenRA.Mods.RA [Desc("Tag trait for things that must be destroyed for a short game to end.")] public class MustBeDestroyedInfo : TraitInfo { } public class MustBeDestroyed { } - - [Desc("Provides game mode information for players/observers.", - "Goes on WorldActor - observers don't have a player it can live on.")] - public class ConquestObjectivesPanelInfo : ITraitInfo - { - public string ObjectivesPanel = null; - public object Create(ActorInitializer init) { return new ConquestObjectivesPanel(this); } - } - - public class ConquestObjectivesPanel : IObjectivesPanel - { - ConquestObjectivesPanelInfo info; - public ConquestObjectivesPanel(ConquestObjectivesPanelInfo info) { this.info = info; } - public string ObjectivesPanel { get { return info.ObjectivesPanel; } } - } } diff --git a/OpenRA.Mods.RA/MenuPaletteEffect.cs b/OpenRA.Mods.RA/MenuPaletteEffect.cs index feee5ea158..b206fff4fc 100644 --- a/OpenRA.Mods.RA/MenuPaletteEffect.cs +++ b/OpenRA.Mods.RA/MenuPaletteEffect.cs @@ -21,9 +21,12 @@ namespace OpenRA.Mods.RA [Desc("Time (in ticks) to fade between states")] public readonly int FadeLength = 10; - [Desc("Effect style to fade to. Accepts values of None or Desaturated")] + [Desc("Effect style to fade to during gameplay. Accepts values of None or Desaturated.")] public readonly MenuPaletteEffect.EffectType Effect = MenuPaletteEffect.EffectType.None; + [Desc("Effect style to fade to when opening the in-game menu. Accepts values of None, Black or Desaturated.")] + public readonly MenuPaletteEffect.EffectType MenuEffect = MenuPaletteEffect.EffectType.None; + public object Create(ActorInitializer init) { return new MenuPaletteEffect(this); } } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 8d182014a0..d62d7d5450 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -373,6 +373,10 @@ + + + + diff --git a/OpenRA.Mods.RA/Player/MissionObjectives.cs b/OpenRA.Mods.RA/Player/MissionObjectives.cs index 7fdbcf42c5..fc0b5632d5 100644 --- a/OpenRA.Mods.RA/Player/MissionObjectives.cs +++ b/OpenRA.Mods.RA/Player/MissionObjectives.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Linq; using OpenRA.Primitives; @@ -77,6 +78,7 @@ namespace OpenRA.Mods.RA objectives.Insert(newID, new MissionObjective(type, description)); + ObjectiveAdded(player); foreach (var imo in player.PlayerActor.TraitsImplementing()) imo.OnObjectiveAdded(player, newID); @@ -195,8 +197,26 @@ namespace OpenRA.Mods.RA } } + public event Action ObjectiveAdded = player => { }; + public void OnObjectiveAdded(Player player, int id) {} public void OnObjectiveCompleted(Player player, int id) {} public void OnObjectiveFailed(Player player, int id) {} } + + [Desc("Provides game mode progress information for players.", + "Goes on WorldActor - observers don't have a player it can live on.", + "Current options for PanelName are 'SKIRMISH_STATS' and 'MISSION_OBJECTIVES'.")] + public class ObjectivesPanelInfo : ITraitInfo + { + public string PanelName = null; + public object Create(ActorInitializer init) { return new ObjectivesPanel(this); } + } + + public class ObjectivesPanel : IObjectivesPanel + { + ObjectivesPanelInfo info; + public ObjectivesPanel(ObjectivesPanelInfo info) { this.info = info; } + public string PanelName { get { return info.PanelName; } } + } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/DebugMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/DebugMenuLogic.cs index e1f27035f7..8b56cef6a3 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/DebugMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/DebugMenuLogic.cs @@ -18,7 +18,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic public class DebugMenuLogic { [ObjectCreator.UseCtor] - public DebugMenuLogic(Widget widget, Action onExit, World world, bool transient) + public DebugMenuLogic(Widget widget, World world) { var devTrait = world.LocalPlayer.PlayerActor.Trait(); @@ -112,21 +112,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic showAstarCostCheckbox.IsChecked = () => dbgOverlay != null ? dbgOverlay.Visible : false; showAstarCostCheckbox.OnClick = () => { if (dbgOverlay != null) dbgOverlay.Visible ^= true; }; } - - var close = widget.GetOrNull("CLOSE"); - if (close != null) - { - close.OnClick = () => - { - if (transient) - { - Ui.CloseWindow(); - Ui.Root.RemoveChild(widget); - } - - onExit(); - }; - } } public void Order(World world, string order) diff --git a/OpenRA.Mods.RA/Widgets/Logic/DiplomacyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/DiplomacyLogic.cs index 6a3d822f15..4f2301ac32 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/DiplomacyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/DiplomacyLogic.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic ScrollPanelWidget diplomacyPanel; [ObjectCreator.UseCtor] - public DiplomacyLogic(Widget widget, Action onExit, World world, bool transient) + public DiplomacyLogic(Widget widget, Action onExit, World world) { this.world = world; @@ -33,18 +33,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic var close = widget.GetOrNull("CLOSE"); if (close != null) - { close.OnClick = () => { - if (transient) - { - Ui.CloseWindow(); - Ui.Root.RemoveChild(widget); - } - + Ui.CloseWindow(); + Ui.Root.RemoveChild(widget); onExit(); }; - } } void LayoutPlayers() diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs new file mode 100644 index 0000000000..32114e4d59 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs @@ -0,0 +1,38 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Widgets; +using OpenRA.Traits; +using OpenRA.Mods.RA; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class GameInfoBriefingLogic + { + [ObjectCreator.UseCtor] + public GameInfoBriefingLogic(Widget widget, World world) + { + var previewWidget = widget.Get("MAP_PREVIEW"); + previewWidget.Preview = () => Game.modData.MapCache[world.Map.Uid]; + + var mapDescriptionPanel = widget.Get("MAP_DESCRIPTION_PANEL"); + var mapDescription = widget.Get("MAP_DESCRIPTION"); + var mapFont = Game.Renderer.Fonts[mapDescription.Font]; + var text = world.Map.Description != null ? world.Map.Description.Replace("\\n", "\n") : ""; + text = WidgetUtils.WrapText(text, mapDescription.Bounds.Width, mapFont); + mapDescription.Text = text; + mapDescription.Bounds.Height = mapFont.Measure(text).Y; + mapDescriptionPanel.ScrollToTop(); + mapDescriptionPanel.Layout.AdjustChildren(); + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoLogic.cs new file mode 100644 index 0000000000..23b5dc9541 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -0,0 +1,109 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Widgets; +using OpenRA.Traits; +using OpenRA.Mods.RA; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + public enum IngameInfoPanel { AutoSelect, Map, Objectives, Debug }; + + class GameInfoLogic + { + [ObjectCreator.UseCtor] + public GameInfoLogic(Widget widget, World world, IngameInfoPanel activePanel) + { + var lp = world.LocalPlayer; + var numTabs = 0; + + widget.IsVisible = () => activePanel != IngameInfoPanel.AutoSelect; + + // Objectives/Stats tab + var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); + if (lp != null && iop != null && iop.PanelName != null) + { + numTabs++; + var objectivesTabButton = widget.Get(string.Concat("BUTTON", numTabs.ToString())); + objectivesTabButton.GetText = () => "Objectives"; + objectivesTabButton.IsVisible = () => lp != null && numTabs > 1; + objectivesTabButton.OnClick = () => activePanel = IngameInfoPanel.Objectives; + objectivesTabButton.IsHighlighted = () => activePanel == IngameInfoPanel.Objectives; + + var objectivesPanel = widget.Get("OBJECTIVES_PANEL"); + objectivesPanel.IsVisible = () => activePanel == IngameInfoPanel.Objectives; + + Game.LoadWidget(world, iop.PanelName, objectivesPanel, new WidgetArgs()); + + if (activePanel == IngameInfoPanel.AutoSelect) + activePanel = IngameInfoPanel.Objectives; + } + + // Briefing tab + if (world.Map.CustomPreview != null) + { + numTabs++; + var mapTabButton = widget.Get(string.Concat("BUTTON", numTabs.ToString())); + mapTabButton.Text = "Briefing"; + mapTabButton.IsVisible = () => numTabs > 1; + mapTabButton.OnClick = () => activePanel = IngameInfoPanel.Map; + mapTabButton.IsHighlighted = () => activePanel == IngameInfoPanel.Map; + + var mapPanel = widget.Get("MAP_PANEL"); + mapPanel.IsVisible = () => activePanel == IngameInfoPanel.Map; + + Game.LoadWidget(world, "MAP_PANEL", mapPanel, new WidgetArgs()); + + if (activePanel == IngameInfoPanel.AutoSelect) + activePanel = IngameInfoPanel.Map; + } + + // Debug/Cheats tab + if (lp != null && world.LobbyInfo.GlobalSettings.AllowCheats) + { + numTabs++; + var debugTabButton = widget.Get(string.Concat("BUTTON", numTabs.ToString())); + debugTabButton.Text = "Debug"; + debugTabButton.IsVisible = () => lp != null && world.LobbyInfo.GlobalSettings.AllowCheats && numTabs > 1; + debugTabButton.OnClick = () => activePanel = IngameInfoPanel.Debug; + debugTabButton.IsHighlighted = () => activePanel == IngameInfoPanel.Debug; + + var debugPanelContainer = widget.Get("DEBUG_PANEL"); + debugPanelContainer.IsVisible = () => activePanel == IngameInfoPanel.Debug; + + Game.LoadWidget(world, "DEBUG_PANEL", debugPanelContainer, new WidgetArgs()); + + if (activePanel == IngameInfoPanel.AutoSelect) + activePanel = IngameInfoPanel.Debug; + } + + // Handle empty space when tabs aren't displayed + var titleText = widget.Get("TITLE"); + var titleTextNoTabs = widget.GetOrNull("TITLE_NO_TABS"); + + titleText.IsVisible = () => numTabs > 1 || (numTabs == 1 && titleTextNoTabs == null); + titleText.GetText = () => string.Concat(world.Map.Type, ": ", world.Map.Title); + if (titleTextNoTabs != null) + { + titleTextNoTabs.IsVisible = () => numTabs == 1; + titleTextNoTabs.GetText = () => string.Concat(world.Map.Type, ": ", world.Map.Title); + } + + var bg = widget.Get("BACKGROUND"); + var bgNoTabs = widget.GetOrNull("BACKGROUND_NO_TABS"); + + bg.IsVisible = () => numTabs > 1 || (numTabs == 1 && bgNoTabs == null); + if (bgNoTabs != null) + bgNoTabs.IsVisible = () => numTabs == 1; + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoObjectivesLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoObjectivesLogic.cs new file mode 100644 index 0000000000..b167a47f60 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoObjectivesLogic.cs @@ -0,0 +1,72 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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 System.Drawing; +using OpenRA.Widgets; +using OpenRA.Traits; +using OpenRA.Mods.RA; + +namespace OpenRA.Mods.RA.Widgets.Logic +{ + class GameInfoObjectivesLogic + { + ContainerWidget template; + + [ObjectCreator.UseCtor] + public GameInfoObjectivesLogic(Widget widget, World world) + { + var lp = world.LocalPlayer; + + var missionStatus = widget.Get("MISSION_STATUS"); + missionStatus.GetText = () => lp.WinState == WinState.Undefined ? "In progress" : + lp.WinState == WinState.Won ? "Accomplished" : "Failed"; + missionStatus.GetColor = () => lp.WinState == WinState.Undefined ? Color.White : + lp.WinState == WinState.Won ? Color.LimeGreen : Color.Red; + + var mo = lp.PlayerActor.TraitOrDefault(); + if (mo == null) + return; + + var objectivesPanel = widget.Get("OBJECTIVES_PANEL"); + template = objectivesPanel.Get("OBJECTIVE_TEMPLATE"); + + PopulateObjectivesList(mo, objectivesPanel, template); + + Action RedrawObjectives = player => + { + if (player == lp) + PopulateObjectivesList(mo, objectivesPanel, template); + }; + mo.ObjectiveAdded += RedrawObjectives; + } + + void PopulateObjectivesList(MissionObjectives mo, ScrollPanelWidget parent, ContainerWidget template) + { + parent.RemoveChildren(); + + foreach (var objective in mo.Objectives.OrderBy(o => o.Type)) + { + var widget = template.Clone(); + + var label = widget.Get("OBJECTIVE_TYPE"); + label.GetText = () => objective.Type == ObjectiveType.Primary ? "Primary" : "Secondary"; + + var checkbox = widget.Get("OBJECTIVE_STATUS"); + checkbox.IsChecked = () => objective.State != ObjectiveState.Incomplete; + checkbox.GetCheckType = () => objective.State == ObjectiveState.Completed ? "checked" : "crossed"; + checkbox.GetText = () => objective.Description; + + parent.AddChild(widget); + } + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncConquestObjectivesLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoStatsLogic.cs similarity index 53% rename from OpenRA.Mods.Cnc/Widgets/Logic/CncConquestObjectivesLogic.cs rename to OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoStatsLogic.cs index 9e7e2df3e7..0b56ef02d4 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncConquestObjectivesLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/GameInfoStatsLogic.cs @@ -1,6 +1,6 @@ -#region Copyright & License Information +#region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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, @@ -8,46 +8,42 @@ */ #endregion +using System; using System.Drawing; using System.Linq; -using OpenRA.Mods.RA; using OpenRA.Widgets; +using OpenRA.Traits; +using OpenRA.Mods.RA; -namespace OpenRA.Mods.Cnc.Widgets.Logic +namespace OpenRA.Mods.RA.Widgets.Logic { - public class CncConquestObjectivesLogic + class GameInfoStatsLogic { [ObjectCreator.UseCtor] - public CncConquestObjectivesLogic(Widget widget, World world) + public GameInfoStatsLogic(Widget widget, World world) { - var panel = widget.Get("CONQUEST_OBJECTIVES"); - panel.Get("TITLE").GetText = () => "Conquest: " + world.Map.Title; + var lp = world.LocalPlayer; - var statusLabel = panel.Get("STATUS"); - statusLabel.IsVisible = () => world.LocalPlayer != null; + var checkbox = widget.Get("STATS_CHECKBOX"); + checkbox.IsChecked = () => lp.WinState != WinState.Undefined; + checkbox.GetCheckType = () => lp.WinState == WinState.Won ? + "checked" : "crossed"; - if (world.LocalPlayer != null) - { - var lp = world.LocalPlayer; - var objectiveCheckbox = panel.Get("1"); - objectiveCheckbox.IsChecked = () => lp.WinState != WinState.Undefined; - objectiveCheckbox.GetCheckType = () => lp.WinState == WinState.Won ? - "checked" : "crossed"; + var statusLabel = widget.Get("STATS_STATUS"); - statusLabel.GetText = () => lp.WinState == WinState.Won ? "Complete" : - lp.WinState == WinState.Lost ? "Failed" : "Incomplete"; - statusLabel.GetColor = () => lp.WinState == WinState.Won ? Color.Green : - lp.WinState == WinState.Lost ? Color.Red : Color.White; - } + statusLabel.GetText = () => lp.WinState == WinState.Won ? "Accomplished" : + lp.WinState == WinState.Lost ? "Failed" : "In progress"; + statusLabel.GetColor = () => lp.WinState == WinState.Won ? Color.LimeGreen : + lp.WinState == WinState.Lost ? Color.Red : Color.White; - var scrollpanel = panel.Get("PLAYER_LIST"); - var itemTemplate = scrollpanel.Get("PLAYER_TEMPLATE"); - scrollpanel.RemoveChildren(); + var playerPanel = widget.Get("PLAYER_LIST"); + var playerTemplate = playerPanel.Get("PLAYER_TEMPLATE"); + playerPanel.RemoveChildren(); foreach (var p in world.Players.Where(a => !a.NonCombatant)) { var pp = p; - var item = itemTemplate.Clone(); + var item = playerTemplate.Clone(); var nameLabel = item.Get("NAME"); nameLabel.GetText = () => pp.WinState == WinState.Lost ? pp.PlayerName + " (Dead)" : pp.PlayerName; nameLabel.GetColor = () => pp.Color.RGB; @@ -61,7 +57,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var client = world.LobbyInfo.ClientWithIndex(pp.ClientIndex); var teamNumber = (client == null) ? 0 : client.Team; team.GetText = () => (teamNumber == 0) ? "-" : teamNumber.ToString(); - scrollpanel.AddChild(item); + playerPanel.AddChild(item); var stats = pp.PlayerActor.TraitOrDefault(); if (stats == null) diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs index 2dd149f74d..50fabfb33f 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs @@ -1,4 +1,4 @@ -#region Copyright & License Information +#region Copyright & License Information /* * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made @@ -9,19 +9,34 @@ #endregion using System; +using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.RA; +using OpenRA.Mods.RA.Widgets; +using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.RA.Widgets.Logic { - class IngameMenuLogic + public class IngameMenuLogic { + Widget menu; + [ObjectCreator.UseCtor] - public IngameMenuLogic(Widget widget, World world, Action onExit, WorldRenderer worldRenderer, bool transient) + public IngameMenuLogic(Widget widget, World world, Action onExit, WorldRenderer worldRenderer, IngameInfoPanel activePanel) { var resumeDisabled = false; + menu = widget.Get("INGAME_MENU"); var mpe = world.WorldActor.TraitOrDefault(); + if (mpe != null) + mpe.Fade(mpe.Info.MenuEffect); + menu.Get("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version; + + var hideMenu = false; + menu.Get("MENU_BUTTONS").IsVisible = () => !hideMenu; + + // TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions Action onQuit = () => { Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); @@ -33,7 +48,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic Game.RunAfterDelay(exitDelay, () => mpe.Fade(MenuPaletteEffect.EffectType.Black)); exitDelay += 40 * mpe.Info.FadeLength; } - Game.RunAfterDelay(exitDelay, () => { Game.Disconnect(); @@ -42,62 +56,68 @@ namespace OpenRA.Mods.RA.Widgets.Logic }); }; - Action onSurrender = () => + Action closeMenu = () => { - world.IssueOrder(new Order("Surrender", world.LocalPlayer.PlayerActor, false)); + Ui.CloseWindow(); + Ui.Root.RemoveChild(menu); + if (mpe != null) + mpe.Fade(MenuPaletteEffect.EffectType.None); onExit(); }; - widget.Get("DISCONNECT").OnClick = () => + Action showMenu = () => hideMenu = false; + + menu.Get("ABORT_MISSION").OnClick = () => { - widget.Visible = false; - ConfirmationDialogs.PromptConfirmAction("Abort Mission", "Leave this game and return to the menu?", onQuit, () => widget.Visible = true); + hideMenu = true; + ConfirmationDialogs.PromptConfirmAction("Abort Mission", "Leave this game and return to the menu?", onQuit, showMenu); }; - widget.Get("SETTINGS").OnClick = () => + Action onSurrender = () => { - widget.Visible = false; - Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() - { - { "onExit", () => widget.Visible = true }, - { "worldRenderer", worldRenderer }, - }); + world.IssueOrder(new Order("Surrender", world.LocalPlayer.PlayerActor, false)); + closeMenu(); }; - - widget.Get("MUSIC").OnClick = () => + var surrenderButton = menu.Get("SURRENDER"); + surrenderButton.IsDisabled = () => (world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined); + surrenderButton.OnClick = () => { - widget.Visible = false; - Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs + hideMenu = true; + ConfirmationDialogs.PromptConfirmAction("Surrender", "Are you sure you want to surrender?", onSurrender, showMenu); + }; + widget.Get("SURRENDER").IsVisible = () => world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined; + + menu.Get("MUSIC").OnClick = () => + { + hideMenu = true; + Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs() { - { "onExit", () => { widget.Visible = true; } }, + { "onExit", () => hideMenu = false }, { "world", world } }); }; - var resumeButton = widget.Get("RESUME"); - resumeButton.IsDisabled = () => resumeDisabled; - resumeButton.OnClick = () => + menu.Get("SETTINGS").OnClick = () => { - if (transient) + hideMenu = true; + Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() { - Ui.CloseWindow(); - Ui.Root.RemoveChild(widget); - } - - onExit(); + { "world", world }, + { "worldRenderer", worldRenderer }, + { "onExit", () => hideMenu = false }, + }); }; - widget.Get("SURRENDER").OnClick = () => + var resumeButton = menu.Get("RESUME"); + resumeButton.IsDisabled = () => resumeDisabled; + resumeButton.OnClick = closeMenu; + + // Game info panel + var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", menu, new WidgetArgs() { - widget.Visible = false; - ConfirmationDialogs.PromptConfirmAction( - "Surrender", - "Are you sure you want to surrender?", - onSurrender, - () => widget.Visible = true, - "Surrender"); - }; - widget.Get("SURRENDER").IsVisible = () => world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined; + { "activePanel", activePanel } + }); + gameInfoPanel.IsVisible = () => !hideMenu; } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs index a24b302d21..4d30629241 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic WorldRenderer worldRenderer; [ObjectCreator.UseCtor] - public ObserverStatsLogic(World world, WorldRenderer worldRenderer, Widget widget, Action onExit, bool transient) + public ObserverStatsLogic(World world, WorldRenderer worldRenderer, Widget widget, Action onExit) { this.world = world; this.worldRenderer = worldRenderer; @@ -139,18 +139,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic var close = widget.GetOrNull("CLOSE"); if (close != null) - { close.OnClick = () => { - if (transient) - { - Ui.CloseWindow(); - Ui.Root.RemoveChild(widget); - } - + Ui.CloseWindow(); + Ui.Root.RemoveChild(widget); onExit(); }; - } } void ClearStats() diff --git a/OpenRA.Mods.RA/Widgets/Logic/OrderButtonsChromeLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/OrderButtonsChromeLogic.cs index 3bb5bda0ed..2b5a826f0a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/OrderButtonsChromeLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/OrderButtonsChromeLogic.cs @@ -8,9 +8,11 @@ */ #endregion +using System; using System.Linq; using OpenRA.Mods.RA.Orders; using OpenRA.Widgets; +using OpenRA.Traits; namespace OpenRA.Mods.RA.Widgets.Logic { @@ -59,8 +61,32 @@ namespace OpenRA.Mods.RA.Widgets.Logic var options = widget.GetOrNull("OPTIONS_BUTTON"); if (options != null) { + var blinking = false; + var lp = world.LocalPlayer; options.IsDisabled = () => disableSystemButtons; - options.OnClick = () => OpenMenuPanel(options); + options.OnClick = () => + { + blinking = false; + OpenMenuPanel(options, new WidgetArgs() + { + { "activePanel", IngameInfoPanel.AutoSelect } + }); + }; + options.IsHighlighted = () => blinking && Game.LocalTick % 50 < 25; + + if (lp != null) + { + Action StartBlinking = player => + { + if (player == world.LocalPlayer) + blinking = true; + }; + + var mo = lp.PlayerActor.TraitOrDefault(); + + if (mo != null) + mo.ObjectiveAdded += StartBlinking; + } } var diplomacy = widget.GetOrNull("DIPLOMACY_BUTTON"); @@ -76,7 +102,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic { debug.IsVisible = () => world.LobbyInfo.GlobalSettings.AllowCheats; debug.IsDisabled = () => disableSystemButtons; - debug.OnClick = () => OpenMenuPanel(debug); + debug.OnClick = () => OpenMenuPanel(debug, new WidgetArgs() + { + { "activePanel", IngameInfoPanel.Debug } + }); } var stats = widget.GetOrNull("OBSERVER_STATS_BUTTON"); @@ -87,7 +116,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic } } - void OpenMenuPanel(MenuButtonWidget button) + void OpenMenuPanel(MenuButtonWidget button, WidgetArgs widgetArgs = null) { disableSystemButtons = true; var cachedPause = world.PredictedPaused; @@ -98,21 +127,19 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (button.Pause && world.LobbyInfo.IsSinglePlayer) world.SetPauseState(true); - Game.LoadWidget(world, button.MenuContainer, Ui.Root, new WidgetArgs() + widgetArgs = widgetArgs ?? new WidgetArgs(); + widgetArgs.Add("onExit", () => { - { "transient", true }, - { "onExit", () => - { - if (button.HideIngameUI) - ingameRoot.IsVisible = () => true; + if (button.HideIngameUI) + ingameRoot.IsVisible = () => true; - if (button.Pause && world.LobbyInfo.IsSinglePlayer) - world.SetPauseState(cachedPause); + if (button.Pause && world.LobbyInfo.IsSinglePlayer) + world.SetPauseState(cachedPause); - disableSystemButtons = false; - } - } + disableSystemButtons = false; }); + + Game.LoadWidget(world, button.MenuContainer, Ui.Root, widgetArgs); } static void BindOrderButton(World world, ButtonWidget w, string icon) diff --git a/mods/cnc/chrome/ingame-debug.yaml b/mods/cnc/chrome/ingame-debug.yaml index d7db534ff5..148e0ec559 100644 --- a/mods/cnc/chrome/ingame-debug.yaml +++ b/mods/cnc/chrome/ingame-debug.yaml @@ -3,122 +3,110 @@ Container@DEBUG_PANEL: Width: PARENT_RIGHT Height: PARENT_BOTTOM Children: - Label@TITLE: - Width: PARENT_RIGHT - Y: 0-25 - Font: BigBold - Contrast: true + Label@CHEATS_TITLE: + Y: 25 + Font: Bold + Text: Cheats Align: Center - Text: Debug Options - Background@bg: Width: PARENT_RIGHT - Height: PARENT_BOTTOM - Background: panel-black - Children: - Label@CHEATS_TITLE: - Y: 25 - Font: Bold - Text: Cheats - Align: Center - Width: PARENT_RIGHT - Checkbox@INSTANT_BUILD: - X: 45 - Y: 45 - Width: 200 - Height: 20 - Font: Regular - Text: Instant Build Speed - Checkbox@ENABLE_TECH: - X: 45 - Y: 75 - Width: 200 - Height: 20 - Font: Regular - Text: Build Everything - Checkbox@BUILD_ANYWHERE: - X: 45 - Y: 105 - Width: 200 - Height: 20 - Font: Regular - Text: Build Anywhere - Checkbox@UNLIMITED_POWER: - X: 290 - Y: 45 - Width: 200 - Height: 20 - Font: Regular - Text: Unlimited Power - Checkbox@INSTANT_CHARGE: - X: 290 - Y: 75 - Width: 200 - Height: 20 - Font: Regular - Text: Instant Charge Time - Checkbox@DISABLE_SHROUD: - X: 290 - Y: 105 - Height: 20 - Width: 200 - Font: Regular - Text: Disable Shroud & Fog - Button@GIVE_CASH: - X: 90 - Y: 145 - Width: 140 - Height: 35 - Text: Give $20,000 - Button@GROW_RESOURCES: - X: 271 - Y: 145 - Width: 140 - Height: 35 - Text: Grow Resources - Button@GIVE_EXPLORATION: - X: 90 - Y: 195 - Width: 140 - Height: 35 - Text: Clear Shroud - Button@RESET_EXPLORATION: - X: 271 - Y: 195 - Width: 140 - Height: 35 - Text: Reset Shroud - Label@VISUALIZATIONS_TITLE: - Y: 255 - Font: Bold - Text: Visualizations - Align: Center - Width: PARENT_RIGHT - Checkbox@SHOW_UNIT_PATHS: - X: 45 - Y: 285 - Width: 200 - Height: 20 - Font: Regular - Text: Show Unit Paths - Checkbox@SHOW_ASTAR: - X: 45 - Y: 315 - Height: 20 - Width: 200 - Font: Regular - Text: Show A* Cost - Checkbox@SHOW_COMBATOVERLAY: - X: 290 - Y: 285 - Height: 20 - Width: 200 - Font: Regular - Text: Show Combat Geometry - Checkbox@SHOW_GEOMETRY: - X: 290 - Y: 315 - Height: 20 - Width: 200 - Font: Regular - Text: Show Render Geometry + Checkbox@INSTANT_BUILD: + X: 45 + Y: 45 + Width: 200 + Height: 20 + Font: Regular + Text: Instant Build Speed + Checkbox@ENABLE_TECH: + X: 45 + Y: 75 + Width: 200 + Height: 20 + Font: Regular + Text: Build Everything + Checkbox@BUILD_ANYWHERE: + X: 45 + Y: 105 + Width: 200 + Height: 20 + Font: Regular + Text: Build Anywhere + Checkbox@UNLIMITED_POWER: + X: 290 + Y: 45 + Width: 200 + Height: 20 + Font: Regular + Text: Unlimited Power + Checkbox@INSTANT_CHARGE: + X: 290 + Y: 75 + Width: 200 + Height: 20 + Font: Regular + Text: Instant Charge Time + Checkbox@DISABLE_SHROUD: + X: 290 + Y: 105 + Height: 20 + Width: 200 + Font: Regular + Text: Disable Shroud & Fog + Button@GIVE_CASH: + X: 90 + Y: 145 + Width: 140 + Height: 35 + Text: Give $20,000 + Button@GROW_RESOURCES: + X: 271 + Y: 145 + Width: 140 + Height: 35 + Text: Grow Resources + Button@GIVE_EXPLORATION: + X: 90 + Y: 195 + Width: 140 + Height: 35 + Text: Clear Shroud + Button@RESET_EXPLORATION: + X: 271 + Y: 195 + Width: 140 + Height: 35 + Text: Reset Shroud + Label@VISUALIZATIONS_TITLE: + Y: 255 + Font: Bold + Text: Visualizations + Align: Center + Width: PARENT_RIGHT + Checkbox@SHOW_UNIT_PATHS: + X: 45 + Y: 285 + Width: 200 + Height: 20 + Font: Regular + Text: Show Unit Paths + Checkbox@SHOW_ASTAR: + X: 45 + Y: 315 + Height: 20 + Width: 200 + Font: Regular + Text: Show A* Cost + Checkbox@SHOW_COMBATOVERLAY: + X: 290 + Y: 285 + Height: 20 + Width: 200 + Font: Regular + Text: Show Combat Geometry + Checkbox@SHOW_GEOMETRY: + X: 290 + Y: 315 + Height: 20 + Width: 200 + Font: Regular + Text: Show Render Geometry diff --git a/mods/cnc/chrome/ingame-info.yaml b/mods/cnc/chrome/ingame-info.yaml new file mode 100644 index 0000000000..7b0a99f610 --- /dev/null +++ b/mods/cnc/chrome/ingame-info.yaml @@ -0,0 +1,55 @@ +Container@GAME_INFO_PANEL: + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 512 + Height: 375 + Logic: GameInfoLogic + Visible: False + Children: + Label@TITLE: + Width: PARENT_RIGHT + Y: 0 - 20 + Text: Game Information + Align: Center + Font: BigBold + Contrast: true + Label@TITLE_NO_TABS: + Width: PARENT_RIGHT + Y: 15 + Text: Game Information + Align: Center + Font: BigBold + Contrast: true + Container@TAB_CONTAINER: + Children: + Button@BUTTON1: + Y: 5 + Width: 140 + Height: 35 + Visible: False + Button@BUTTON2: + X: 150 + Y: 5 + Width: 140 + Height: 35 + Visible: False + Button@BUTTON3: + X: 300 + Y: 5 + Width: 140 + Height: 35 + Visible: False + Background@BACKGROUND: + Y: 39 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Background: panel-black + Children: + Container@STATS_PANEL: + Container@MAP_PANEL: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Container@OBJECTIVES_PANEL: + Container@DEBUG_PANEL: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM diff --git a/mods/cnc/chrome/ingame-infobriefing.yaml b/mods/cnc/chrome/ingame-infobriefing.yaml new file mode 100644 index 0000000000..5151240779 --- /dev/null +++ b/mods/cnc/chrome/ingame-infobriefing.yaml @@ -0,0 +1,32 @@ +Container@MAP_PANEL: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoBriefingLogic + Children: + Background@PREVIEW_BG: + X: (PARENT_RIGHT - WIDTH) / 2 + Y: 15 + Width: 324 + Height: 160 + Background: panel-gray + Children: + MapPreview@MAP_PREVIEW: + Width: 320 + Height: 156 + X: 2 + Y: 2 + IgnoreMouseOver: True + IgnoreMouseInput: True + ShowSpawnPoints: False + ScrollPanel@MAP_DESCRIPTION_PANEL: + X: 15 + Y: 190 + Width: 482 + Height: 170 + Children: + Label@MAP_DESCRIPTION: + X: 5 + Y: 195 + Width: 452 + Height: 160 + diff --git a/mods/cnc/chrome/ingame-infoobjectives.yaml b/mods/cnc/chrome/ingame-infoobjectives.yaml new file mode 100644 index 0000000000..b61e92a4af --- /dev/null +++ b/mods/cnc/chrome/ingame-infoobjectives.yaml @@ -0,0 +1,40 @@ +Container@MISSION_OBJECTIVES: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoObjectivesLogic + Children: + Label@MISSION: + X: 15 + Y: 15 + Width: 80 + Height: 20 + Font: MediumBold + Text: Mission: + Label@MISSION_STATUS: + X: 95 + Y: 15 + Width: PARENT_RIGHT - 110 + Height: 20 + Font: MediumBold + ScrollPanel@OBJECTIVES_PANEL: + X: 15 + Y: 50 + Width: 482 + Height: 310 + ItemSpacing: 35 + Children: + Container@OBJECTIVE_TEMPLATE: + Children: + Label@OBJECTIVE_TYPE: + X: 10 + Y: 0 - 20 + Height: 20 + Width: 70 + Align: Center + Checkbox@OBJECTIVE_STATUS: + X: 90 + Y: 0 - 20 + Width: PARENT_RIGHT - 100 + Height: 20 + Disabled: True + TextColorDisabled: 255,255,255 diff --git a/mods/cnc/chrome/ingame-infostats.yaml b/mods/cnc/chrome/ingame-infostats.yaml new file mode 100644 index 0000000000..46dcfa0f0d --- /dev/null +++ b/mods/cnc/chrome/ingame-infostats.yaml @@ -0,0 +1,108 @@ +Container@SKIRMISH_STATS: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoStatsLogic + Children: + Label@STATS_OBJECTIVE: + X: 15 + Y: 10 + Width: 85 + Height: 25 + Font: MediumBold + Text: Mission: + Label@STATS_STATUS: + X: 100 + Y: 10 + Width: PARENT_RIGHT - 10 + Height: 25 + Font: MediumBold + Checkbox@STATS_CHECKBOX: + X: 15 + Y: 45 + Width: 482 + Height: 20 + Font: Bold + Text: Destroy all opposition! + Disabled: yes + TextColorDisabled: 255,255,255 + Container@STATS_HEADERS: + X: 17 + Y: 80 + Width: 393 + Children: + Label@NAME: + X: 10 + Width: 150 + Height: 25 + Text: Player + Font: Bold + Label@RACE: + X: 150 + Width: 80 + Height: 25 + Text: Faction + Font: Bold + Align: Center + Label@STANCE: + X: 240 + Width: 70 + Height: 25 + Text: Team + Font: Bold + Align: Center + Label@KILLS: + X: 310 + Width: 70 + Height: 25 + Text: Kills + Font: Bold + Align: Center + Label@DEATHS: + X: 380 + Width: 70 + Height: 25 + Text: Deaths + Font: Bold + Align: Center + ScrollPanel@PLAYER_LIST: + X: 15 + Y: 105 + Width: 482 + Height: 250 + ItemSpacing: 5 + Children: + Container@PLAYER_TEMPLATE: + Width: PARENT_RIGHT-27 + Height: 25 + X: 2 + Y: 0 + Children: + Label@NAME: + X: 10 + Width: 150 + Height: 25 + Image@FACTIONFLAG: + X: 159 + Y: 6 + Width: 32 + Height: 16 + Label@FACTION: + X: 195 + Width: 40 + Height: 25 + Label@TEAM: + X: 240 + Width: 70 + Height: 25 + Align: Center + Label@KILLS: + X: 310 + Width: 70 + Height: 25 + Align: Center + Label@DEATHS: + X: 380 + Width: 70 + Height: 25 + Align: Center + diff --git a/mods/cnc/chrome/ingame-menu.yaml b/mods/cnc/chrome/ingame-menu.yaml index 606e1789a9..859a57978c 100644 --- a/mods/cnc/chrome/ingame-menu.yaml +++ b/mods/cnc/chrome/ingame-menu.yaml @@ -1,27 +1,7 @@ -Container@INGAME_MENU_PANEL: - X: (WINDOW_RIGHT - WIDTH)/2 - Y: (WINDOW_BOTTOM - HEIGHT)/2 - Width: 512 - Height: 370 - Children: - Button@OBJECTIVES_BUTTON: - Y: PARENT_BOTTOM - 1 - Width: 140 - Height: 35 - Text: Objectives - Visible: false - Button@DEBUG_BUTTON: - X: 150 - Y: PARENT_BOTTOM - 1 - Width: 140 - Height: 35 - Text: Debug - Visible: false - Container@INGAME_MENU: Width: WINDOW_RIGHT Height: WINDOW_BOTTOM - Logic: CncIngameMenuLogic + Logic: IngameMenuLogic Children: Image@EVA: X: WINDOW_RIGHT-128-43 @@ -46,31 +26,31 @@ Container@INGAME_MENU: Width: 740 Height: 35 Children: - Button@QUIT_BUTTON: + Button@ABORT_MISSION: X: 0 Y: 0 Width: 140 Height: 35 Text: Abort Mission - Button@SURRENDER_BUTTON: + Button@SURRENDER: X: 150 Y: 0 Width: 140 Height: 35 Text: Surrender - Button@MUSIC_BUTTON: + Button@MUSIC: X: 300 Y: 0 Width: 140 Height: 35 Text: Music - Button@SETTINGS_BUTTON: + Button@SETTINGS: X: 450 Y: 0 Width: 140 Height: 35 Text: Settings - Button@RESUME_BUTTON: + Button@RESUME: Key: escape X: 600 Y: 0 diff --git a/mods/cnc/chrome/objectives.yaml b/mods/cnc/chrome/objectives.yaml deleted file mode 100644 index 2599c1e804..0000000000 --- a/mods/cnc/chrome/objectives.yaml +++ /dev/null @@ -1,120 +0,0 @@ -Container@CONQUEST_OBJECTIVES: - Logic: CncConquestObjectivesLogic - Width: PARENT_RIGHT - Height: PARENT_BOTTOM - Children: - Label@TITLE: - Width: PARENT_RIGHT - Y: 0-25 - Font: BigBold - Contrast: true - Align: Center - Background@bg: - Width: PARENT_RIGHT - Height: PARENT_BOTTOM - Background: panel-black - Children: - Label@PRIMARY: - X: 15 - Y: 10 - Width: 482 - Height: 25 - Font: MediumBold - Text: Primary Objectives: - Label@STATUS: - X: 190 - Y: 10 - Width: 482 - Height: 25 - Font: MediumBold - Text: Incomplete - Checkbox@1: - X: 25 - Y: 45 - Width: 482 - Height: 20 - Font: Bold - Text: Crush all opposition! - Disabled: yes - Container@LABEL_CONTAINER: - X: 17 - Y: 80 - Width: 393 - Children: - Label@NAME: - X: 10 - Width: 150 - Height: 25 - Text: Player - Align: Center - Font: Bold - Label@RACE: - X: 150 - Width: 80 - Height: 25 - Text: Faction - Font: Bold - Align: Center - Label@STANCE: - X: 240 - Width: 70 - Height: 25 - Text: Team - Font: Bold - Align: Center - Label@KILLS: - X: 310 - Width: 70 - Height: 25 - Text: Kills - Font: Bold - Align: Center - Label@DEATHS: - X: 380 - Width: 70 - Height: 25 - Text: Deaths - Font: Bold - Align: Center - ScrollPanel@PLAYER_LIST: - X: 15 - Y: 105 - Width: 482 - Height: 250 - ItemSpacing: 5 - Children: - Container@PLAYER_TEMPLATE: - Width: PARENT_RIGHT-27 - Height: 25 - X: 2 - Y: 0 - Children: - Label@NAME: - X: 10 - Width: 150 - Height: 25 - Image@FACTIONFLAG: - X: 159 - Y: 6 - Width: 32 - Height: 16 - Label@FACTION: - X: 195 - Width: 40 - Height: 25 - Label@TEAM: - X: 240 - Width: 70 - Height: 25 - Align: Center - Label@KILLS: - X: 310 - Width: 70 - Height: 25 - Align: Center - Label@DEATHS: - X: 380 - Width: 70 - Height: 25 - Align: Center - diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 13479e27d0..dafb8b1977 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -88,13 +88,16 @@ ChromeLayout: mods/cnc/chrome/replaybrowser.yaml mods/cnc/chrome/ingame.yaml mods/cnc/chrome/ingame-chat.yaml - mods/cnc/chrome/ingame-debug.yaml mods/cnc/chrome/ingame-menu.yaml + mods/cnc/chrome/ingame-debug.yaml + mods/cnc/chrome/ingame-info.yaml + mods/cnc/chrome/ingame-infobriefing.yaml + mods/cnc/chrome/ingame-infoobjectives.yaml + mods/cnc/chrome/ingame-infostats.yaml mods/cnc/chrome/music.yaml mods/cnc/chrome/settings.yaml mods/cnc/chrome/credits.yaml mods/cnc/chrome/dialogs.yaml - mods/cnc/chrome/objectives.yaml mods/cnc/chrome/tooltips.yaml mods/cnc/chrome/irc.yaml mods/cnc/chrome/assetbrowser.yaml diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 2014d5aff4..fe0e1d98e3 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -8,6 +8,7 @@ World: LoadWidgetAtGameStart: Widget: INGAME_ROOT MenuPaletteEffect: + MenuEffect: Desaturated CloakPaletteEffect: ScreenShaker: NukePaletteEffect: @@ -185,7 +186,7 @@ World: PathFinder: ValidateOrder: DebugPauseState: - ConquestObjectivesPanel: - ObjectivesPanel: CONQUEST_OBJECTIVES + ObjectivesPanel: + PanelName: SKIRMISH_STATS RadarPings: diff --git a/mods/d2k/chrome/ingame-menu.yaml b/mods/d2k/chrome/ingame-menu.yaml new file mode 100644 index 0000000000..bcddf2682d --- /dev/null +++ b/mods/d2k/chrome/ingame-menu.yaml @@ -0,0 +1,61 @@ +Container@INGAME_MENU: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + Logic: IngameMenuLogic + Children: + Label@VERSION_LABEL: + X: WINDOW_RIGHT - 10 + Y: WINDOW_BOTTOM - 20 + Align: Right + Font: Regular + Contrast: True + Background@MENU_BUTTONS: + X: 100 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 200 + Height: 295 + Children: + Label@LABEL_TITLE: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 20 + Width: 200 + Height: 30 + Text: Options + Align: Center + Font: Bold + Button@RESUME: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 60 + Width: 140 + Height: 30 + Text: Resume + Font: Bold + Key: escape + Button@SETTINGS: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 100 + Width: 140 + Height: 30 + Text: Settings + Font: Bold + Button@MUSIC: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 140 + Width: 140 + Height: 30 + Text: Music + Font: Bold + Button@SURRENDER: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 180 + Width: 140 + Height: 30 + Text: Surrender + Font: Bold + Button@ABORT_MISSION: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 220 + Width: 140 + Height: 30 + Text: Abort Mission + Font: Bold diff --git a/mods/d2k/chrome/ingame-observer.yaml b/mods/d2k/chrome/ingame-observer.yaml index 996d72c9b4..3aa2336b2d 100644 --- a/mods/d2k/chrome/ingame-observer.yaml +++ b/mods/d2k/chrome/ingame-observer.yaml @@ -1,6 +1,10 @@ Container@OBSERVER_WIDGETS: Children: - Button@OBSERVER_STATS_BUTTON: + MenuButton@OBSERVER_STATS_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_OBSERVERSTATS_BG + HideIngameUI: False + Pause: False X: 162 Y: 0 Width: 160 diff --git a/mods/d2k/chrome/ingame-player.yaml b/mods/d2k/chrome/ingame-player.yaml index 38f2eb8fbf..e60ff102b8 100644 --- a/mods/d2k/chrome/ingame-player.yaml +++ b/mods/d2k/chrome/ingame-player.yaml @@ -3,7 +3,10 @@ Container@PLAYER_WIDGETS: LogicKeyListener@CONTROLGROUP_KEYHANDLER: Logic: ControlGroupLogic LogicTicker@SIDEBAR_TICKER: - Button@INGAME_DIPLOMACY_BUTTON: + MenuButton@DIPLOMACY_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_DIPLOMACY_BG + HideIngameUI: False X: 162 Y: 0 Width: 160 @@ -11,24 +14,16 @@ Container@PLAYER_WIDGETS: Text: Diplomacy (P) Font: Bold Key: p - Button@INGAME_DEBUG_BUTTON: + MenuButton@DEBUG_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_MENU X: 324 Y: 0 Width: 160 Height: 25 Text: Debug (Shift + Esc) - Visible: false Font: Bold Key: escape Shift - Button@OBJECTIVES_BUTTON: - X: 486 - Y: 0 - Width: 160 - Height: 25 - Text: Objectives (O) - Visible: false - Font: Bold - Key: o SlidingContainer@INGAME_RADAR_BIN: X: WINDOW_RIGHT-215 Y: 0 diff --git a/mods/d2k/chrome/ingame.yaml b/mods/d2k/chrome/ingame.yaml index 6fb6d8f353..ba99368f3b 100644 --- a/mods/d2k/chrome/ingame.yaml +++ b/mods/d2k/chrome/ingame.yaml @@ -46,7 +46,8 @@ Container@INGAME_ROOT: Y: 34 Order: Descending Container@PLAYER_ROOT: - Button@INGAME_OPTIONS_BUTTON: + MenuButton@OPTIONS_BUTTON: + Logic: OrderButtonsChromeLogic X: 0 Y: 0 Width: 160 diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index e27ad056d8..cc9d32dd33 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -61,7 +61,11 @@ ChromeLayout: mods/ra/chrome/ingame-chat.yaml mods/ra/chrome/ingame-diplomacy.yaml mods/ra/chrome/ingame-fmvplayer.yaml - mods/ra/chrome/ingame-menu.yaml + mods/d2k/chrome/ingame-menu.yaml + mods/ra/chrome/ingame-info.yaml + mods/ra/chrome/ingame-infobriefing.yaml + mods/ra/chrome/ingame-infoobjectives.yaml + mods/ra/chrome/ingame-infostats.yaml mods/d2k/chrome/ingame-observer.yaml mods/ra/chrome/ingame-observerstats.yaml mods/d2k/chrome/ingame-player.yaml @@ -152,6 +156,9 @@ Fonts: Title: Font:mods/d2k/Dune2k.ttf Size:32 + MediumBold: + Font:FreeSansBold.ttf + Size:18 BigBold: Font:FreeSansBold.ttf Size:24 diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index 6df1ce8aec..d1dfee1296 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -189,4 +189,6 @@ World: ValidateOrder: DebugPauseState: RadarPings: + ObjectivesPanel: + PanelName: SKIRMISH_STATS diff --git a/mods/ra/chrome/ingame-debug.yaml b/mods/ra/chrome/ingame-debug.yaml index b10dd915b9..b2e20db205 100644 --- a/mods/ra/chrome/ingame-debug.yaml +++ b/mods/ra/chrome/ingame-debug.yaml @@ -1,110 +1,117 @@ -Background@INGAME_DEBUG_BG: +Container@DEBUG_PANEL: Logic: DebugMenuLogic - X: (WINDOW_RIGHT - WIDTH)/2 - Y: (WINDOW_BOTTOM - HEIGHT)/2 - Width: 350 - Height: 475 + Y: 20 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM Children: - Label@LABEL_TITLE: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 20 - Width: 250 - Height: 25 + Label@LABLE_TITLE: + Y: 25 + Font: Bold Text: Debug Options Align: Center - Font: Bold - Checkbox@DISABLE_SHROUD: - X: 30 - Y: 50 - Height: 20 - Width: PARENT_RIGHT - 30 - Text: Disable Shroud & Fog - Button@GIVE_EXPLORATION: - X: 30 - Y: 80 - Width: 120 - Height: 25 - Text: Give Exploration - Button@RESET_EXPLORATION: - X: 200 - Y: 80 - Width: 120 - Height: 25 - Text: Reset Exploration - Checkbox@SHOW_UNIT_PATHS: - X: 30 - Y: 110 - Width: PARENT_RIGHT - 30 - Height: 20 - Text: Show Unit Paths - Button@GIVE_CASH: - X: 30 - Y: 140 - Width: 135 - Height: 20 - Text: Give $20000 Cash - Height: 25 - Button@GROW_RESOURCES: - X: 185 - Y: 140 - Width: 135 - Height: 20 - Text: Grow Resources - Height: 25 + Width: PARENT_RIGHT Checkbox@INSTANT_BUILD: - X: 30 - Y: 170 - Width: PARENT_RIGHT - 30 + X: 45 + Y: 45 + Width: 200 Height: 20 + Font: Regular Text: Instant Build Speed - Checkbox@INSTANT_CHARGE: - X: 30 - Y: 200 - Width: PARENT_RIGHT - 30 - Height: 20 - Text: Support Powers Charge Instantly Checkbox@ENABLE_TECH: - X: 30 - Y: 230 - Width: PARENT_RIGHT - 30 + X: 45 + Y: 75 + Width: 200 Height: 20 + Font: Regular Text: Build Everything - Checkbox@UNLIMITED_POWER: - X: 30 - Y: 260 - Width: PARENT_RIGHT - 30 - Height: 20 - Text: Unlimited Power Checkbox@BUILD_ANYWHERE: - X: 30 - Y: 290 - Width: PARENT_RIGHT - 30 + X: 45 + Y: 105 + Width: 200 Height: 20 + Font: Regular Text: Build Anywhere - Checkbox@SHOW_ASTAR: - X: 30 - Y: 320 - Width: PARENT_RIGHT - 30 + Checkbox@UNLIMITED_POWER: + X: 290 + Y: 45 + Width: 200 Height: 20 + Font: Regular + Text: Unlimited Power + Checkbox@INSTANT_CHARGE: + X: 290 + Y: 75 + Width: 200 + Height: 20 + Font: Regular + Text: Instant Charge Time + Checkbox@DISABLE_SHROUD: + X: 290 + Y: 105 + Height: 20 + Width: 200 + Font: Regular + Text: Disable Shroud & Fog + Button@GIVE_CASH: + X: 90 + Y: 150 + Width: 140 + Height: 30 + Font: Bold + Text: Give $20,000 + Button@GROW_RESOURCES: + X: 271 + Y: 150 + Width: 140 + Height: 30 + Font: Bold + Text: Grow Resources + Button@GIVE_EXPLORATION: + X: 90 + Y: 200 + Width: 140 + Height: 30 + Font: Bold + Text: Clear Shroud + Button@RESET_EXPLORATION: + X: 271 + Y: 200 + Width: 140 + Height: 30 + Font: Bold + Text: Reset Shroud + Label@VISUALIZATIONS_TITLE: + Y: 255 + Font: Bold + Text: Visualizations + Align: Center + Width: PARENT_RIGHT + Checkbox@SHOW_UNIT_PATHS: + X: 45 + Y: 285 + Width: 200 + Height: 20 + Font: Regular + Text: Show Unit Paths + Checkbox@SHOW_ASTAR: + X: 45 + Y: 315 + Height: 20 + Width: 200 + Font: Regular Text: Show A* Cost Checkbox@SHOW_COMBATOVERLAY: - X: 30 - Y: 350 + X: 290 + Y: 285 Height: 20 Width: 200 + Font: Regular Text: Show Combat Geometry Checkbox@SHOW_GEOMETRY: - X: 30 - Y: 380 + X: 290 + Y: 315 Height: 20 Width: 200 + Font: Regular Text: Show Render Geometry - Button@CLOSE: - X: 30 - Y: 420 - Width: PARENT_RIGHT - 60 - Height: 25 - Text: Close - Key: escape - Font: Bold diff --git a/mods/ra/chrome/ingame-info.yaml b/mods/ra/chrome/ingame-info.yaml new file mode 100644 index 0000000000..6aad3f056d --- /dev/null +++ b/mods/ra/chrome/ingame-info.yaml @@ -0,0 +1,64 @@ +Container@GAME_INFO_PANEL: + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 522 + Height: 455 + Logic: GameInfoLogic + Visible: False + Children: + Background@BACKGROUND: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Background@BACKGROUND_NO_TABS: + Y: 25 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM - 25 + Label@TITLE: + Y: 20 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Font: Bold + Label@TITLE_NO_TABS: + Y: 45 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Font: Bold + Container@TAB_CONTAINER: + X: (PARENT_RIGHT - WIDTH) / 2 + Width: 360 + Height: 25 + Children: + Button@BUTTON1: + Y: 50 + Width: 120 + Height: 25 + Font: Bold + Visible: False + Button@BUTTON2: + X: 120 + Y: 50 + Width: 120 + Height: 25 + Font: Bold + Visible: False + Button@BUTTON3: + X: 240 + Y: 50 + Width: 120 + Height: 25 + Font: Bold + Visible: False + Container@STATS_PANEL: + Y: 65 + Container@MAP_PANEL: + Y: 65 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Container@OBJECTIVES_PANEL: + Y: 65 + Container@DEBUG_PANEL: + Y: 65 + Width: PARENT_RIGHT + Height: PARENT_BOTTOM diff --git a/mods/ra/chrome/ingame-infobriefing.yaml b/mods/ra/chrome/ingame-infobriefing.yaml new file mode 100644 index 0000000000..de0813f7fe --- /dev/null +++ b/mods/ra/chrome/ingame-infobriefing.yaml @@ -0,0 +1,32 @@ +Container@MAP_PANEL: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoBriefingLogic + Children: + Background@PREVIEW_BG: + X: (PARENT_RIGHT - WIDTH) / 2 + Y: 20 + Width: 324 + Height: 160 + Background: panel-gray + Children: + MapPreview@MAP_PREVIEW: + Width: 320 + Height: 156 + X: 2 + Y: 2 + IgnoreMouseOver: True + IgnoreMouseInput: True + ShowSpawnPoints: False + ScrollPanel@MAP_DESCRIPTION_PANEL: + X: 20 + Y: 195 + Width: 482 + Height: 175 + Children: + Label@MAP_DESCRIPTION: + X: 5 + Y: 180 + Width: 452 + Height: 145 + diff --git a/mods/ra/chrome/ingame-infoobjectives.yaml b/mods/ra/chrome/ingame-infoobjectives.yaml new file mode 100644 index 0000000000..1dde1deee0 --- /dev/null +++ b/mods/ra/chrome/ingame-infoobjectives.yaml @@ -0,0 +1,40 @@ +Container@MISSION_OBJECTIVES: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoObjectivesLogic + Children: + Label@MISSION: + X: 20 + Y: 20 + Width: 80 + Height: 25 + Font: MediumBold + Text: Mission: + Label@MISSION_STATUS: + X: 100 + Y: 20 + Width: PARENT_RIGHT - 120 + Height: 25 + Font: MediumBold + ScrollPanel@OBJECTIVES_PANEL: + X: 20 + Y: 60 + Width: 482 + Height: 310 + ItemSpacing: 35 + Children: + Container@OBJECTIVE_TEMPLATE: + Children: + Label@OBJECTIVE_TYPE: + X: 10 + Y: 0 - 20 + Height: 20 + Width: 70 + Align: Center + Checkbox@OBJECTIVE_STATUS: + X: 90 + Y: 0 - 20 + Width: PARENT_RIGHT - 100 + Height: 20 + Disabled: True + TextColorDisabled: 255,255,255 diff --git a/mods/ra/chrome/ingame-infostats.yaml b/mods/ra/chrome/ingame-infostats.yaml new file mode 100644 index 0000000000..4c8d45ddcd --- /dev/null +++ b/mods/ra/chrome/ingame-infostats.yaml @@ -0,0 +1,108 @@ +Container@SKIRMISH_STATS: + Height: PARENT_BOTTOM + Width: PARENT_RIGHT + Logic: GameInfoStatsLogic + Children: + Label@MISSION: + X: 20 + Y: 20 + Width: 482 + Height: 25 + Font: MediumBold + Text: Mission: + Label@STATS_STATUS: + X: 100 + Y: 20 + Width: PARENT_RIGHT - 10 + Height: 25 + Font: MediumBold + Checkbox@STATS_CHECKBOX: + X: 20 + Y: 55 + Width: 482 + Height: 20 + Font: Bold + Text: Destroy all opposition! + Disabled: yes + TextColorDisabled: 255,255,255 + Container@STATS_HEADERS: + X: 22 + Y: 80 + Width: 393 + Children: + Label@NAME: + X: 10 + Width: 150 + Height: 25 + Text: Player + Font: Bold + Label@RACE: + X: 150 + Width: 80 + Height: 25 + Text: Faction + Font: Bold + Align: Center + Label@STANCE: + X: 240 + Width: 70 + Height: 25 + Text: Team + Font: Bold + Align: Center + Label@KILLS: + X: 310 + Width: 70 + Height: 25 + Text: Kills + Font: Bold + Align: Center + Label@DEATHS: + X: 380 + Width: 70 + Height: 25 + Text: Deaths + Font: Bold + Align: Center + ScrollPanel@PLAYER_LIST: + X: 20 + Y: 105 + Width: 482 + Height: 265 + ItemSpacing: 5 + Children: + Container@PLAYER_TEMPLATE: + Width: PARENT_RIGHT-27 + Height: 25 + X: 2 + Y: 0 + Children: + Label@NAME: + X: 10 + Width: 150 + Height: 25 + Image@FACTIONFLAG: + X: 159 + Y: 6 + Width: 32 + Height: 16 + Label@FACTION: + X: 195 + Width: 40 + Height: 25 + Label@TEAM: + X: 240 + Width: 70 + Height: 25 + Align: Center + Label@KILLS: + X: 310 + Width: 70 + Height: 25 + Align: Center + Label@DEATHS: + X: 380 + Width: 70 + Height: 25 + Align: Center + diff --git a/mods/ra/chrome/ingame-menu.yaml b/mods/ra/chrome/ingame-menu.yaml index 73ad81ea76..f3483b91e7 100644 --- a/mods/ra/chrome/ingame-menu.yaml +++ b/mods/ra/chrome/ingame-menu.yaml @@ -1,52 +1,74 @@ -Background@INGAME_OPTIONS_BG: - X: (WINDOW_RIGHT - WIDTH)/2 - Y: (WINDOW_BOTTOM - HEIGHT)/2 - Width: 300 - Height: 295 +Container@INGAME_MENU: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM Logic: IngameMenuLogic Children: - Label@LABEL_TITLE: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 20 - Width: 250 + Background@BORDER: + X: 0 - 15 + Y: 0 - 15 + Width: WINDOW_RIGHT + 30 + Height: WINDOW_BOTTOM + 30 + Background: mainmenu-border + Image@LOGO: + X: WINDOW_RIGHT - 296 + Y: 30 + ImageCollection: logos + ImageName: logo + Label@VERSION_LABEL: + X: WINDOW_RIGHT - 296 + Y: 296 - 20 + Width: 296 - 20 Height: 25 - Text: Options Align: Center - Font: Bold - Button@RESUME: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 60 - Width: 160 - Height: 25 - Text: Resume - Font: Bold - Key: escape - Button@SETTINGS: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 100 - Width: 160 - Height: 25 - Text: Settings - Font: Bold - Button@MUSIC: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 140 - Width: 160 - Height: 25 - Text: Music - Font: Bold - Button@SURRENDER: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 180 - Width: 160 - Height: 25 - Text: Surrender - Font: Bold - Button@DISCONNECT: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 220 - Width: 160 - Height: 25 - Text: Abort Mission - Font: Bold - + Font: Regular + Contrast: True + Background@MENU_BUTTONS: + X: 100 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 200 + Height: 295 + Children: + Label@LABEL_TITLE: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 20 + Width: 200 + Height: 30 + Text: Options + Align: Center + Font: Bold + Button@RESUME: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 60 + Width: 140 + Height: 30 + Text: Resume + Font: Bold + Key: escape + Button@SETTINGS: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 100 + Width: 140 + Height: 30 + Text: Settings + Font: Bold + Button@MUSIC: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 140 + Width: 140 + Height: 30 + Text: Music + Font: Bold + Button@SURRENDER: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 180 + Width: 140 + Height: 30 + Text: Surrender + Font: Bold + Button@ABORT_MISSION: + X: (PARENT_RIGHT - WIDTH)/2 + Y: 220 + Width: 140 + Height: 30 + Text: Abort Mission + Font: Bold diff --git a/mods/ra/chrome/ingame-objectives.yaml b/mods/ra/chrome/ingame-objectives.yaml deleted file mode 100644 index 87b6bc02fa..0000000000 --- a/mods/ra/chrome/ingame-objectives.yaml +++ /dev/null @@ -1,98 +0,0 @@ -Background@MISSION_OBJECTIVES: - Logic: MissionObjectivesLogic - X: 25 - Y: 50 - Width: 512 - Height: 530 - Visible: false - Background: dialog - Children: - Label@TITLE: - X: 0 - Y: 15 - Width: PARENT_RIGHT - Height: 25 - Font: Bold - Align: Center - Text: Objectives - Label@PRIMARY_OBJECTIVE_HEADER: - X: 40 - Y: 40 - Width: 300 - Height: 25 - Font: Bold - Text: Primary Objectives - Label@PRIMARY_STATUS_HEADER: - X: 350 - Y: 40 - Width: 122 - Height: 25 - Font: Bold - Text: Status - ScrollPanel@PRIMARY_OBJECTIVES: - X: 25 - Y: 70 - Width: PARENT_RIGHT-50 - Height: 200 - ItemSpacing: 20 - Children: - Container@PRIMARY_OBJECTIVE_TEMPLATE: - X: 15 - Y: 0 - Width: PARENT_RIGHT - Children: - Label@PRIMARY_OBJECTIVE: - X: 0 - Y: 0 - Width: 300 - Height: PARENT_BOTTOM - Font: Regular - WordWrap: True - Label@PRIMARY_STATUS: - X: 310 - Y: 0 - Width: 122 - Height: PARENT_BOTTOM - Font: Bold - WordWrap: True - Label@SECONDARY_OBJECTIVE_HEADER: - X: 40 - Y: 275 - Width: 300 - Height: 25 - Font: Bold - Text: Secondary Objectives - Label@SECONDARY_STATUS_HEADER: - X: 350 - Y: 275 - Width: 122 - Height: 25 - Font: Bold - Text: Status - ScrollPanel@SECONDARY_OBJECTIVES: - X: 25 - Y: 305 - Width: PARENT_RIGHT-50 - Height: 200 - ItemSpacing: 20 - Children: - Container@SECONDARY_OBJECTIVE_TEMPLATE: - X: 15 - Y: 0 - Width: PARENT_RIGHT - Children: - Label@SECONDARY_OBJECTIVE: - X: 0 - Y: 0 - Width: 300 - Height: PARENT_BOTTOM - Font: Regular - WordWrap: True - Label@SECONDARY_STATUS: - X: 310 - Y: 0 - Width: 122 - Height: PARENT_BOTTOM - Font: Bold - WordWrap: True - diff --git a/mods/ra/chrome/ingame-observer.yaml b/mods/ra/chrome/ingame-observer.yaml index 06c37dead3..e751dac3f1 100644 --- a/mods/ra/chrome/ingame-observer.yaml +++ b/mods/ra/chrome/ingame-observer.yaml @@ -21,8 +21,8 @@ Container@OBSERVER_WIDGETS: Font: Bold Contrast: true MenuButton@OPTIONS_BUTTON: - MenuContainer: INGAME_OPTIONS_BG - HideIngameUI: false + MenuContainer: INGAME_MENU + HideIngameUI: true Pause: false X: 0 Y: 0 diff --git a/mods/ra/chrome/ingame-player.yaml b/mods/ra/chrome/ingame-player.yaml index fb72542e65..e75d3e083e 100644 --- a/mods/ra/chrome/ingame-player.yaml +++ b/mods/ra/chrome/ingame-player.yaml @@ -3,15 +3,6 @@ Container@PLAYER_WIDGETS: LogicKeyListener@CONTROLGROUP_KEYHANDLER: Logic: ControlGroupLogic LogicTicker@SIDEBAR_TICKER: - Button@OBJECTIVES_BUTTON: - X: 486 - Y: 0 - Width: 160 - Height: 25 - Text: Objectives (O) - Visible: false - Font: Bold - Key: o Container@SUPPORT_POWERS: Logic: SupportPowerBinLogic X: 10 @@ -106,9 +97,6 @@ Container@PLAYER_WIDGETS: ImageCollection: order-icons MenuButton@DEBUG_BUTTON: Logic: AddRaceSuffixLogic - MenuContainer: INGAME_DEBUG_BG - HideIngameUI: false - Pause: false Key: escape Shift X: 128 Width: 28 @@ -144,8 +132,6 @@ Container@PLAYER_WIDGETS: ImageName: diplomacy MenuButton@OPTIONS_BUTTON: Logic: AddRaceSuffixLogic - MenuContainer: INGAME_OPTIONS_BG - HideIngameUI: false Key: escape X: 192 Width: 28 diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 9043bd1e6e..cbcbc3e30f 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -74,8 +74,11 @@ ChromeLayout: mods/ra/chrome/ingame-chat.yaml mods/ra/chrome/ingame-diplomacy.yaml mods/ra/chrome/ingame-fmvplayer.yaml + mods/ra/chrome/ingame-info.yaml + mods/ra/chrome/ingame-infobriefing.yaml + mods/ra/chrome/ingame-infoobjectives.yaml + mods/ra/chrome/ingame-infostats.yaml mods/ra/chrome/ingame-menu.yaml - mods/ra/chrome/ingame-objectives.yaml mods/ra/chrome/ingame-observer.yaml mods/ra/chrome/ingame-observerstats.yaml mods/ra/chrome/ingame-player.yaml @@ -169,6 +172,9 @@ Fonts: Title: Font:mods/ra/ZoodRangmah.ttf Size:48 + MediumBold: + Font:FreeSansBold.ttf + Size:18 BigBold: Font:FreeSansBold.ttf Size:24 diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index 4f34728d95..49cd27c7fd 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -174,4 +174,6 @@ World: DebugPauseState: RadarPings: StartGameNotification: + ObjectivesPanel: + PanelName: SKIRMISH_STATS diff --git a/mods/ts/chrome/ingame-player.yaml b/mods/ts/chrome/ingame-player.yaml index 79c93d15f4..3804db6a7e 100644 --- a/mods/ts/chrome/ingame-player.yaml +++ b/mods/ts/chrome/ingame-player.yaml @@ -3,7 +3,10 @@ Container@PLAYER_WIDGETS: LogicKeyListener@CONTROLGROUP_KEYHANDLER: Logic: ControlGroupLogic LogicTicker@SIDEBAR_TICKER: - Button@INGAME_DIPLOMACY_BUTTON: + MenuButton@DIPLOMACY_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_DIPLOMACY_BG + HideIngameUI: False X: 162 Y: 0 Width: 160 @@ -11,7 +14,9 @@ Container@PLAYER_WIDGETS: Text: Diplomacy (P) Font: Bold Key: p - Button@INGAME_DEBUG_BUTTON: + MenuButton@DEBUG_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_MENU X: 324 Y: 0 Width: 160 @@ -20,15 +25,6 @@ Container@PLAYER_WIDGETS: Visible: false Font: Bold Key: escape Shift - Button@OBJECTIVES_BUTTON: - X: 486 - Y: 0 - Width: 160 - Height: 25 - Text: Objectives (O) - Visible: false - Font: Bold - Key: o SlidingContainer@INGAME_RADAR_BIN: X: WINDOW_RIGHT-215 Y: 0 diff --git a/mods/ts/chrome/ingame.yaml b/mods/ts/chrome/ingame.yaml index bc6ed0b61f..c470852efe 100644 --- a/mods/ts/chrome/ingame.yaml +++ b/mods/ts/chrome/ingame.yaml @@ -46,7 +46,9 @@ Container@INGAME_ROOT: Y: 34 Order: Descending Container@PLAYER_ROOT: - Button@INGAME_OPTIONS_BUTTON: + MenuButton@OPTIONS_BUTTON: + Logic: OrderButtonsChromeLogic + MenuContainer: INGAME_MENU X: 0 Y: 0 Width: 160 diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index bd7ce9714d..0c1e5c35eb 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -104,7 +104,10 @@ ChromeLayout: mods/ra/chrome/ingame-diplomacy.yaml mods/ra/chrome/ingame-fmvplayer.yaml mods/ra/chrome/ingame-menu.yaml - mods/ra/chrome/ingame-objectives.yaml + mods/ra/chrome/ingame-info.yaml + mods/ra/chrome/ingame-infobriefing.yaml + mods/ra/chrome/ingame-infoobjectives.yaml + mods/ra/chrome/ingame-infostats.yaml mods/ra/chrome/ingame-observer.yaml mods/ra/chrome/ingame-observerstats.yaml mods/ts/chrome/ingame-player.yaml From 819eb6401167ca815e60a5c256e56620c0da1576 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Thu, 31 Jul 2014 21:24:08 +0200 Subject: [PATCH 08/13] Add end game dialog --- OpenRA.Game/Widgets/Widget.cs | 5 + .../Widgets/Logic/IngameChromeLogic.cs | 8 ++ OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + .../Widgets/Logic/Ingame/LeaveMapLogic.cs | 70 +++++++++++++ .../LoadIngamePlayerOrObserverUILogic.cs | 8 ++ mods/cnc/chrome/ingame-leavemap.yaml | 98 ++++++++++++++++++ mods/cnc/mod.yaml | 1 + mods/d2k/chrome/ingame-leavemap.yaml | 92 +++++++++++++++++ mods/d2k/mod.yaml | 1 + mods/ra/chrome/ingame-leavemap.yaml | 99 +++++++++++++++++++ mods/ra/mod.yaml | 1 + mods/ts/mod.yaml | 1 + 12 files changed, 385 insertions(+) create mode 100644 OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs create mode 100644 mods/cnc/chrome/ingame-leavemap.yaml create mode 100644 mods/d2k/chrome/ingame-leavemap.yaml create mode 100644 mods/ra/chrome/ingame-leavemap.yaml diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index 030fba1e7e..4ef77ecd88 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -51,6 +51,11 @@ namespace OpenRA.Widgets return window; } + public static Widget CurrentWindow() + { + return WindowList.Count > 0 ? WindowList.Peek() : null; + } + public static T LoadWidget(string id, Widget parent, WidgetArgs args) where T : Widget { var widget = LoadWidget(id, parent, args) as T; diff --git a/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs b/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs index a7f254bc0a..4c7ddeb0d2 100644 --- a/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs +++ b/OpenRA.Mods.D2k/Widgets/Logic/IngameChromeLogic.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Drawing; using System.Linq; using OpenRA.Mods.RA; @@ -43,6 +44,13 @@ namespace OpenRA.Mods.D2k.Widgets.Logic void InitRootWidgets() { Game.LoadWidget(world, "CHAT_PANEL", gameRoot, new WidgetArgs()); + + Action ShowLeaveRestartDialog = () => + { + gameRoot.IsVisible = () => false; + Game.LoadWidget(world, "LEAVE_RESTART_WIDGET", Ui.Root, new WidgetArgs()); + }; + world.GameOver += ShowLeaveRestartDialog; } void InitObserverWidgets() diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index d62d7d5450..85dab4f5b9 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -377,6 +377,7 @@ + diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs new file mode 100644 index 0000000000..f8f1405e54 --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LeaveMapLogic.cs @@ -0,0 +1,70 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Linq; +using OpenRA.Network; +using OpenRA.Traits; +using OpenRA.Widgets; +using OpenRA.Mods.RA; + +namespace OpenRA.Mods.RA.Widgets +{ + class LeaveMapLogic + { + [ObjectCreator.UseCtor] + public LeaveMapLogic(Widget widget, OrderManager orderManager, World world) + { + widget.Get("VERSION_LABEL").Text = Game.modData.Manifest.Mod.Version; + + var panelName = "LEAVE_RESTART_SIMPLE"; + + var iop = world.WorldActor.TraitsImplementing().FirstOrDefault(); + var showObjectives = iop != null && iop.PanelName != null && world.LocalPlayer != null; + + if (showObjectives) + panelName = "LEAVE_RESTART_FULL"; + + var dialog = widget.Get(panelName); + var fmvPlayer = Ui.Root.GetOrNull("FMVPLAYER"); + dialog.IsVisible = () => true; + widget.IsVisible = () => fmvPlayer == null || fmvPlayer != Ui.CurrentWindow(); + + var leaveButton = dialog.Get("LEAVE_BUTTON"); + leaveButton.OnClick = () => + { + leaveButton.Disabled = true; + var mpe = world.WorldActor.TraitOrDefault(); + + Sound.PlayNotification(world.Map.Rules, null, "Speech", "Leave", + world.LocalPlayer == null ? null : world.LocalPlayer.Country.Race); + + var exitDelay = 1200; + if (mpe != null) + { + Game.RunAfterDelay(exitDelay, () => mpe.Fade(MenuPaletteEffect.EffectType.Black)); + exitDelay += 40 * mpe.Info.FadeLength; + } + + Game.RunAfterDelay(exitDelay, () => + { + Game.Disconnect(); + Ui.ResetAll(); + Game.LoadShellMap(); + }); + }; + + if (showObjectives) + { + var objectivesContainer = dialog.Get("OBJECTIVES"); + Game.LoadWidget(world, iop.PanelName, objectivesContainer, new WidgetArgs()); + } + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/Widgets/Logic/Ingame/LoadIngamePlayerOrObserverUILogic.cs b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LoadIngamePlayerOrObserverUILogic.cs index 201007bd86..91e1f7c60a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/Ingame/LoadIngamePlayerOrObserverUILogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/Ingame/LoadIngamePlayerOrObserverUILogic.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using OpenRA.Widgets; namespace OpenRA.Mods.RA.Widgets.Logic @@ -26,6 +27,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic Game.LoadWidget(world, "PLAYER_WIDGETS", playerRoot, new WidgetArgs()); Game.LoadWidget(world, "CHAT_PANEL", ingameRoot, new WidgetArgs()); + + Action ShowLeaveRestartDialog = () => + { + ingameRoot.IsVisible = () => false; + Game.LoadWidget(world, "LEAVE_RESTART_WIDGET", Ui.Root, new WidgetArgs()); + }; + world.GameOver += ShowLeaveRestartDialog; } } } diff --git a/mods/cnc/chrome/ingame-leavemap.yaml b/mods/cnc/chrome/ingame-leavemap.yaml new file mode 100644 index 0000000000..1b150db223 --- /dev/null +++ b/mods/cnc/chrome/ingame-leavemap.yaml @@ -0,0 +1,98 @@ +Container@LEAVE_RESTART_WIDGET: + Logic: LeaveMapLogic + Children: + Image@EVA: + X: WINDOW_RIGHT-128-43 + Y: 43 + Width: 128 + Height: 64 + ImageCollection: logos + ImageName: eva + Label@VERSION_LABEL: + X: WINDOW_RIGHT-128-43 + Y: 115 + Width: 128 + Align: Center + Contrast: true + Background@BORDER: + Width: WINDOW_RIGHT + Height: WINDOW_BOTTOM + Background: shellmapborder + Container@LEAVE_RESTART_SIMPLE + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 370 + Height: 125 + Visible: False + Children: + Background@LEAVE_RESTART_SIMPLE_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM - 35 + Background: panel-black + Children: + Label@GAME_ENDED_LABEL: + X: 0 + Y: 0 - 25 + Width: PARENT_RIGHT + Font: BigBold + Align: Center + Text: The game has ended + Contrast: True + Label@BLURB: + X: 15 + Y: (PARENT_BOTTOM - HEIGHT) / 2 + Width: PARENT_RIGHT - 30 + Text: Press 'Leave' to return to the main menu. + Align: Center + Button@LEAVE_BUTTON: + X: 0 + Y: PARENT_BOTTOM - 1 + Width: 140 + Height: 35 + Font: Bold + Text: Leave + Button@RESTART_BUTTON: + X: 150 + Y: PARENT_BOTTOM - 1 + Width: 140 + Height: 35 + Font: Bold + Text: Restart + Visible: false + Container@LEAVE_RESTART_FULL: + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 512 + Height: 410 + Visible: False + Children: + Background@LEAVE_RESTART_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM - 35 + Background: panel-black + Children: + Label@GAME_ENDED_LABEL: + X: 0 + Y: 0 - 25 + Width: PARENT_RIGHT + Font: BigBold + Align: Center + Text: The game has ended + Contrast: True + Button@LEAVE_BUTTON: + X: 0 + Y: PARENT_BOTTOM - 1 + Width: 140 + Height: 35 + Font: Bold + Text: Leave + Button@RESTART_BUTTON: + X: 150 + Y: PARENT_BOTTOM - 1 + Width: 140 + Height: 35 + Font: Bold + Text: Restart + Visible: false + Container@OBJECTIVES: + diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index dafb8b1977..38afa45035 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -94,6 +94,7 @@ ChromeLayout: mods/cnc/chrome/ingame-infobriefing.yaml mods/cnc/chrome/ingame-infoobjectives.yaml mods/cnc/chrome/ingame-infostats.yaml + mods/cnc/chrome/ingame-leavemap.yaml mods/cnc/chrome/music.yaml mods/cnc/chrome/settings.yaml mods/cnc/chrome/credits.yaml diff --git a/mods/d2k/chrome/ingame-leavemap.yaml b/mods/d2k/chrome/ingame-leavemap.yaml new file mode 100644 index 0000000000..cd78d046f0 --- /dev/null +++ b/mods/d2k/chrome/ingame-leavemap.yaml @@ -0,0 +1,92 @@ +Container@LEAVE_RESTART_WIDGET: + Logic: LeaveMapLogic + Children: + Background@BORDER: + X: 0 - 15 + Y: 0 - 15 + Width: WINDOW_RIGHT + 30 + Height: WINDOW_BOTTOM + 30 + Background: mainmenu-border + Label@VERSION_LABEL: + X: WINDOW_RIGHT - 10 + Y: WINDOW_BOTTOM - 20 + Align: Right + Font: Regular + Contrast: True + Container@LEAVE_RESTART_SIMPLE + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 370 + Height: 175 + Visible: False + Children: + Background@LEAVE_RESTART_SIMPLE_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@GAME_ENDED_LABEL: + Y: 20 + Width: PARENT_RIGHT + Height: 25 + Font: Bold + Align: Center + Text: The game has ended + Label@BLURB: + X: 15 + Y: 50 + Width: PARENT_RIGHT - 30 + Height: 65 + Text: Press 'Leave' to return to the main menu. + Align: Center + Button@RESTART_BUTTON: + X: 20 + Y: PARENT_BOTTOM - 45 + Width: 140 + Height: 25 + Font: Bold + Text: Restart + Visible: false + Button@LEAVE_BUTTON: + X: (PARENT_RIGHT - WIDTH) / 2 + Y: PARENT_BOTTOM - 45 + Width: 140 + Height: 25 + Font: Bold + Text: Leave + Container@LEAVE_RESTART_FULL: + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 522 + Height: 470 + Visible: False + Children: + Background@LEAVE_RESTART_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@GAME_ENDED_LABEL: + X: 20 + Y: 20 + Width: PARENT_RIGHT - 40 + Height: 25 + Font: Bold + Align: Center + Text: The game has ended + Button@RESTART_BUTTON: + X: PARENT_RIGHT - 2 * (WIDTH + 20) + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Font: Bold + Text: Restart + Visible: false + Button@LEAVE_BUTTON: + X: PARENT_RIGHT - WIDTH - 20 + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Font: Bold + Text: Leave + Container@OBJECTIVES: + Y: 40 + diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index cc9d32dd33..e4703fe110 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -70,6 +70,7 @@ ChromeLayout: mods/ra/chrome/ingame-observerstats.yaml mods/d2k/chrome/ingame-player.yaml mods/ra/chrome/ingame-debug.yaml + mods/d2k/chrome/ingame-leavemap.yaml mods/d2k/chrome/mainmenu.yaml mods/ra/chrome/settings.yaml mods/ra/chrome/credits.yaml diff --git a/mods/ra/chrome/ingame-leavemap.yaml b/mods/ra/chrome/ingame-leavemap.yaml new file mode 100644 index 0000000000..1bebfd9716 --- /dev/null +++ b/mods/ra/chrome/ingame-leavemap.yaml @@ -0,0 +1,99 @@ +Container@LEAVE_RESTART_WIDGET: + Logic: LeaveMapLogic + Children: + Background@BORDER: + X: 0 - 15 + Y: 0 - 15 + Width: WINDOW_RIGHT + 30 + Height: WINDOW_BOTTOM + 30 + Background: mainmenu-border + Image@LOGO: + X: WINDOW_RIGHT - 296 + Y: 30 + ImageCollection: logos + ImageName: logo + Label@VERSION_LABEL: + X: WINDOW_RIGHT - 296 + Y: 296 - 20 + Width: 296 - 20 + Height: 25 + Align: Center + Font: Regular + Contrast: True + Container@LEAVE_RESTART_SIMPLE + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 370 + Height: 175 + Visible: False + Children: + Background@LEAVE_RESTART_SIMPLE_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@GAME_ENDED_LABEL: + Y: 20 + Width: PARENT_RIGHT + Height: 25 + Font: Bold + Align: Center + Text: The game has ended + Label@BLURB: + X: 15 + Y: 50 + Width: PARENT_RIGHT - 30 + Height: 65 + Text: Press 'Leave' to return to the main menu. + Align: Center + Button@RESTART_BUTTON: + X: 20 + Y: PARENT_BOTTOM - 45 + Width: 140 + Height: 25 + Font: Bold + Text: Restart + Visible: false + Button@LEAVE_BUTTON: + X: (PARENT_RIGHT - WIDTH) / 2 + Y: PARENT_BOTTOM - 45 + Width: 140 + Height: 25 + Font: Bold + Text: Leave + Container@LEAVE_RESTART_FULL: + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 522 + Height: 470 + Visible: False + Children: + Background@LEAVE_RESTART_BG: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Children: + Label@GAME_ENDED_LABEL: + X: 20 + Y: 20 + Width: PARENT_RIGHT - 40 + Height: 25 + Font: Bold + Align: Center + Text: The game has ended + Button@RESTART_BUTTON: + X: PARENT_RIGHT - 2 * (WIDTH + 20) + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Font: Bold + Text: Restart + Visible: false + Button@LEAVE_BUTTON: + X: PARENT_RIGHT - WIDTH - 20 + Y: PARENT_BOTTOM - 45 + Width: 120 + Height: 25 + Font: Bold + Text: Leave + Container@OBJECTIVES: + Y: 40 + diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index cbcbc3e30f..1bb66eab72 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -78,6 +78,7 @@ ChromeLayout: mods/ra/chrome/ingame-infobriefing.yaml mods/ra/chrome/ingame-infoobjectives.yaml mods/ra/chrome/ingame-infostats.yaml + mods/ra/chrome/ingame-leavemap.yaml mods/ra/chrome/ingame-menu.yaml mods/ra/chrome/ingame-observer.yaml mods/ra/chrome/ingame-observerstats.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 0c1e5c35eb..e889a0e0ae 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -112,6 +112,7 @@ ChromeLayout: mods/ra/chrome/ingame-observerstats.yaml mods/ts/chrome/ingame-player.yaml mods/ra/chrome/ingame-debug.yaml + mods/ra/chrome/ingame-leavemap.yaml mods/ra/chrome/mainmenu.yaml mods/ra/chrome/settings.yaml mods/ra/chrome/credits.yaml From dc33e0e1b02ceaeb4706323bc8c30b8e3ec019d1 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Sun, 27 Jul 2014 13:32:17 +0200 Subject: [PATCH 09/13] Make necessary changes to existing maps --- mods/cnc/maps/gdi01/gdi01.lua | 4 ++-- mods/cnc/maps/gdi01/map.yaml | 4 ++++ mods/cnc/maps/gdi02/gdi02.lua | 4 ++-- mods/cnc/maps/gdi02/map.yaml | 4 ++++ mods/cnc/maps/gdi03/gdi03.lua | 4 ++-- mods/cnc/maps/gdi03/map.yaml | 4 ++++ mods/cnc/maps/gdi04a/gdi04a.lua | 4 ++-- mods/cnc/maps/gdi04a/map.yaml | 4 ++++ mods/cnc/maps/gdi04b/gdi04b.lua | 4 ++-- mods/cnc/maps/gdi04b/map.yaml | 4 ++++ mods/cnc/maps/gdi04c/gdi04c.lua | 4 ++-- mods/cnc/maps/gdi04c/map.yaml | 4 ++++ mods/cnc/maps/nod01/map.yaml | 4 ++++ mods/cnc/maps/nod01/nod01.lua | 4 ++-- mods/cnc/maps/nod03a/map.yaml | 4 ++++ mods/cnc/maps/nod03a/nod03a.lua | 4 ++-- mods/cnc/maps/nod03b/map.yaml | 4 ++++ mods/cnc/maps/nod03b/nod03b.lua | 4 ++-- mods/ra/maps/allies-01-classic/allies01.lua | 4 ++-- mods/ra/maps/allies-01-classic/map.yaml | 4 ++++ mods/ra/maps/allies-02-classic/allies02.lua | 4 ++-- mods/ra/maps/allies-02-classic/map.yaml | 4 ++++ mods/ra/maps/intervention/map.yaml | 4 ++++ mods/ra/maps/intervention/mission.lua | 4 ++-- mods/ra/maps/koth-athena/map.yaml | 3 +++ mods/ra/maps/koth-crossroads/map.yaml | 3 +++ 26 files changed, 78 insertions(+), 24 deletions(-) diff --git a/mods/cnc/maps/gdi01/gdi01.lua b/mods/cnc/maps/gdi01/gdi01.lua index 4cb5b1faa1..52fbc5f8e7 100644 --- a/mods/cnc/maps/gdi01/gdi01.lua +++ b/mods/cnc/maps/gdi01/gdi01.lua @@ -3,12 +3,12 @@ VehicleReinforcements = { "jeep" } NodPatrol = { "e1", "e1" } MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("consyard.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi01/map.yaml b/mods/cnc/maps/gdi01/map.yaml index 072923e57e..4a82cf473e 100644 --- a/mods/cnc/maps/gdi01/map.yaml +++ b/mods/cnc/maps/gdi01/map.yaml @@ -459,8 +459,12 @@ Rules: Loop: false LuaScriptInterface: LuaScripts: gdi01.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true ^Infantry: MustBeDestroyed: PROC: diff --git a/mods/cnc/maps/gdi02/gdi02.lua b/mods/cnc/maps/gdi02/gdi02.lua index 978fcf0265..ea566859d3 100644 --- a/mods/cnc/maps/gdi02/gdi02.lua +++ b/mods/cnc/maps/gdi02/gdi02.lua @@ -5,12 +5,12 @@ VehicleReinforcements = { "jeep" } AttackerSquadSize = 3 MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("flag.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi02/map.yaml b/mods/cnc/maps/gdi02/map.yaml index 08a82ce766..402d22c050 100644 --- a/mods/cnc/maps/gdi02/map.yaml +++ b/mods/cnc/maps/gdi02/map.yaml @@ -736,8 +736,12 @@ Rules: -CrateSpawner: LuaScriptInterface: LuaScripts: gdi02.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true PROC: Buildable: Prerequisites: ~disabled diff --git a/mods/cnc/maps/gdi03/gdi03.lua b/mods/cnc/maps/gdi03/gdi03.lua index 8809c80b20..988d060522 100644 --- a/mods/cnc/maps/gdi03/gdi03.lua +++ b/mods/cnc/maps/gdi03/gdi03.lua @@ -1,10 +1,10 @@ MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("bombaway.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi03/map.yaml b/mods/cnc/maps/gdi03/map.yaml index 5deae0f5e8..1b5b6ecf1c 100644 --- a/mods/cnc/maps/gdi03/map.yaml +++ b/mods/cnc/maps/gdi03/map.yaml @@ -897,8 +897,12 @@ Rules: -CrateSpawner: LuaScriptInterface: LuaScripts: gdi03.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true ^Infantry: MustBeDestroyed: WEAP: diff --git a/mods/cnc/maps/gdi04a/gdi04a.lua b/mods/cnc/maps/gdi04a/gdi04a.lua index 4835100100..c911c0ecdf 100644 --- a/mods/cnc/maps/gdi04a/gdi04a.lua +++ b/mods/cnc/maps/gdi04a/gdi04a.lua @@ -127,11 +127,11 @@ WorldLoaded = function() end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("burdet1.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi04a/map.yaml b/mods/cnc/maps/gdi04a/map.yaml index c0f56925fb..f99bae6bc2 100644 --- a/mods/cnc/maps/gdi04a/map.yaml +++ b/mods/cnc/maps/gdi04a/map.yaml @@ -546,8 +546,12 @@ Rules: -CrateSpawner: LuaScriptInterface: LuaScripts: gdi04a.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true ^Infantry: MustBeDestroyed: ^Vehicle: diff --git a/mods/cnc/maps/gdi04b/gdi04b.lua b/mods/cnc/maps/gdi04b/gdi04b.lua index f0774926b7..ba071d47e7 100644 --- a/mods/cnc/maps/gdi04b/gdi04b.lua +++ b/mods/cnc/maps/gdi04b/gdi04b.lua @@ -162,11 +162,11 @@ WorldLoaded = function() end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("burdet1.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi04b/map.yaml b/mods/cnc/maps/gdi04b/map.yaml index 98e836adc3..03790c811b 100644 --- a/mods/cnc/maps/gdi04b/map.yaml +++ b/mods/cnc/maps/gdi04b/map.yaml @@ -625,8 +625,12 @@ Rules: -CrateSpawner: LuaScriptInterface: LuaScripts: gdi04b.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true ^Infantry: MustBeDestroyed: ^Vehicle: diff --git a/mods/cnc/maps/gdi04c/gdi04c.lua b/mods/cnc/maps/gdi04c/gdi04c.lua index e1d33804d0..df75bd136c 100644 --- a/mods/cnc/maps/gdi04c/gdi04c.lua +++ b/mods/cnc/maps/gdi04c/gdi04c.lua @@ -109,11 +109,11 @@ WorldLoaded = function() end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("burdet1.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("gameover.vqa") end diff --git a/mods/cnc/maps/gdi04c/map.yaml b/mods/cnc/maps/gdi04c/map.yaml index 35a8b22e92..91929a834d 100644 --- a/mods/cnc/maps/gdi04c/map.yaml +++ b/mods/cnc/maps/gdi04c/map.yaml @@ -895,8 +895,12 @@ Rules: -CrateSpawner: LuaScriptInterface: LuaScripts: gdi04c.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true ^Infantry: MustBeDestroyed: ^Vehicle: diff --git a/mods/cnc/maps/nod01/map.yaml b/mods/cnc/maps/nod01/map.yaml index fc6b415d6e..1419f6cb4a 100644 --- a/mods/cnc/maps/nod01/map.yaml +++ b/mods/cnc/maps/nod01/map.yaml @@ -299,12 +299,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: nod01.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES C10: Tooltip: Name: Nikoomba diff --git a/mods/cnc/maps/nod01/nod01.lua b/mods/cnc/maps/nod01/nod01.lua index 7628104eb4..6db3abfa2d 100644 --- a/mods/cnc/maps/nod01/nod01.lua +++ b/mods/cnc/maps/nod01/nod01.lua @@ -2,11 +2,11 @@ RifleInfantryReinforcements = { "e1", "e1", } RocketInfantryReinforcements = { "e3", "e3", "e3" } MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("nodlose.vqa") end diff --git a/mods/cnc/maps/nod03a/map.yaml b/mods/cnc/maps/nod03a/map.yaml index 34d7eb053a..b61ce9c898 100644 --- a/mods/cnc/maps/nod03a/map.yaml +++ b/mods/cnc/maps/nod03a/map.yaml @@ -565,12 +565,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: nod03a.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES NUK2: Buildable: Prerequisites: ~disabled diff --git a/mods/cnc/maps/nod03a/nod03a.lua b/mods/cnc/maps/nod03a/nod03a.lua index ab8fae4894..f1cf69e834 100644 --- a/mods/cnc/maps/nod03a/nod03a.lua +++ b/mods/cnc/maps/nod03a/nod03a.lua @@ -2,12 +2,12 @@ FirstAttackWave = { "e1", "e1", "e1", "e2", } SecondThirdAttackWave = { "e1", "e1", "e2", } MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("desflees.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("flag.vqa") end diff --git a/mods/cnc/maps/nod03b/map.yaml b/mods/cnc/maps/nod03b/map.yaml index 5ef9dc9f71..9f3b265393 100644 --- a/mods/cnc/maps/nod03b/map.yaml +++ b/mods/cnc/maps/nod03b/map.yaml @@ -634,12 +634,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: nod03b.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES NUK2: Buildable: Prerequisites: ~disabled diff --git a/mods/cnc/maps/nod03b/nod03b.lua b/mods/cnc/maps/nod03b/nod03b.lua index 8171a58770..88a6037a87 100644 --- a/mods/cnc/maps/nod03b/nod03b.lua +++ b/mods/cnc/maps/nod03b/nod03b.lua @@ -3,12 +3,12 @@ SecondAttackWave = { "e1", "e1", "e1", } ThirdAttackWave = { "e1", "e1", "e1", "e2", } MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("desflees.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("flag.vqa") end diff --git a/mods/ra/maps/allies-01-classic/allies01.lua b/mods/ra/maps/allies-01-classic/allies01.lua index df16280cdc..d9fbed7ae5 100644 --- a/mods/ra/maps/allies-01-classic/allies01.lua +++ b/mods/ra/maps/allies-01-classic/allies01.lua @@ -105,12 +105,12 @@ CreateEinstein = function() end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) --Media.PlayMovieFullscreen("snowbomb.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("bmap.vqa") end diff --git a/mods/ra/maps/allies-01-classic/map.yaml b/mods/ra/maps/allies-01-classic/map.yaml index 3e9eafda8f..427eb01ffc 100644 --- a/mods/ra/maps/allies-01-classic/map.yaml +++ b/mods/ra/maps/allies-01-classic/map.yaml @@ -574,12 +574,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: allies01.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES TRAN.Extraction: Inherits: TRAN RenderUnit: diff --git a/mods/ra/maps/allies-02-classic/allies02.lua b/mods/ra/maps/allies-02-classic/allies02.lua index 18834f4241..616478a37c 100644 --- a/mods/ra/maps/allies-02-classic/allies02.lua +++ b/mods/ra/maps/allies-02-classic/allies02.lua @@ -23,12 +23,12 @@ RunInitialActivities = function() end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) Media.PlayMovieFullscreen("montpass.vqa") end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) Media.PlayMovieFullscreen("frozen.vqa") end diff --git a/mods/ra/maps/allies-02-classic/map.yaml b/mods/ra/maps/allies-02-classic/map.yaml index c075d75304..ab1c51bbf1 100644 --- a/mods/ra/maps/allies-02-classic/map.yaml +++ b/mods/ra/maps/allies-02-classic/map.yaml @@ -869,12 +869,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: allies02.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES ^Infantry: MustBeDestroyed: ^Tank: diff --git a/mods/ra/maps/intervention/map.yaml b/mods/ra/maps/intervention/map.yaml index 00669d8f17..1a8b95c1c1 100644 --- a/mods/ra/maps/intervention/map.yaml +++ b/mods/ra/maps/intervention/map.yaml @@ -2214,12 +2214,16 @@ Smudges: Rules: Player: -ConquestVictoryConditions: + MissionObjectives: + EarlyGameOver: true World: -CrateSpawner: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: LuaScripts: mission.lua + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES CAMERA: RevealsShroud: Range: 18c0 diff --git a/mods/ra/maps/intervention/mission.lua b/mods/ra/maps/intervention/mission.lua index ab3f9156f9..36aef85741 100644 --- a/mods/ra/maps/intervention/mission.lua +++ b/mods/ra/maps/intervention/mission.lua @@ -264,9 +264,9 @@ WorldLoaded = function() end MissionFailed = function() - Mission.MissionOver(nil, { player }, false) + Mission.MissionOver(nil, { player }, true) end MissionAccomplished = function() - Mission.MissionOver({ player }, nil, false) + Mission.MissionOver({ player }, nil, true) end diff --git a/mods/ra/maps/koth-athena/map.yaml b/mods/ra/maps/koth-athena/map.yaml index 31441ff12b..d8a89a439b 100644 --- a/mods/ra/maps/koth-athena/map.yaml +++ b/mods/ra/maps/koth-athena/map.yaml @@ -2119,6 +2119,9 @@ Rules: ResetOnHoldLost: true RatioRequired: 1 CriticalRatioRequired: 1 + World: + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Sequences: diff --git a/mods/ra/maps/koth-crossroads/map.yaml b/mods/ra/maps/koth-crossroads/map.yaml index 9e70dd1f57..71e684ce8e 100644 --- a/mods/ra/maps/koth-crossroads/map.yaml +++ b/mods/ra/maps/koth-crossroads/map.yaml @@ -270,6 +270,9 @@ Rules: ResetOnHoldLost: true RatioRequired: 1 CriticalRatioRequired: 1 + World: + ObjectivesPanel: + PanelName: MISSION_OBJECTIVES Sequences: From d673520d825f018015efc39c80e9c2761633abca Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 4 Aug 2014 00:34:38 +0200 Subject: [PATCH 10/13] Remove inconsistencies w.r.t the Surrender order * Surrendering is now possible even in maps that do not use ConquestVictoryConditions. * The Surrender button is greyed out instead of hidden when it cannot be used. --- OpenRA.Mods.RA/ConquestVictoryConditions.cs | 2 +- OpenRA.Mods.RA/Player/MissionObjectives.cs | 9 ++++++++- OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/OpenRA.Mods.RA/ConquestVictoryConditions.cs b/OpenRA.Mods.RA/ConquestVictoryConditions.cs index f4e277d44c..8e37a578ea 100644 --- a/OpenRA.Mods.RA/ConquestVictoryConditions.cs +++ b/OpenRA.Mods.RA/ConquestVictoryConditions.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new ConquestVictoryConditions(init.self, this); } } - public class ConquestVictoryConditions : ITick, IResolveOrder, INotifyObjectivesUpdated + public class ConquestVictoryConditions : ITick, INotifyObjectivesUpdated { readonly ConquestVictoryConditionsInfo info; readonly MissionObjectives mo; diff --git a/OpenRA.Mods.RA/Player/MissionObjectives.cs b/OpenRA.Mods.RA/Player/MissionObjectives.cs index fc0b5632d5..8c907495eb 100644 --- a/OpenRA.Mods.RA/Player/MissionObjectives.cs +++ b/OpenRA.Mods.RA/Player/MissionObjectives.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new MissionObjectives(init.world, this); } } - public class MissionObjectives : INotifyObjectivesUpdated, ISync + public class MissionObjectives : INotifyObjectivesUpdated, ISync, IResolveOrder { readonly MissionObjectivesInfo info; readonly List objectives = new List(); @@ -202,6 +202,13 @@ namespace OpenRA.Mods.RA public void OnObjectiveAdded(Player player, int id) {} public void OnObjectiveCompleted(Player player, int id) {} public void OnObjectiveFailed(Player player, int id) {} + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "Surrender") + for (var id = 0; id < objectives.Count; id++) + MarkFailed(self.Owner, id); + } } [Desc("Provides game mode progress information for players.", diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs index 50fabfb33f..eff7114022 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameMenuLogic.cs @@ -85,7 +85,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic hideMenu = true; ConfirmationDialogs.PromptConfirmAction("Surrender", "Are you sure you want to surrender?", onSurrender, showMenu); }; - widget.Get("SURRENDER").IsVisible = () => world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined; + surrenderButton.IsDisabled = () => world.LocalPlayer == null || world.LocalPlayer.WinState != WinState.Undefined; menu.Get("MUSIC").OnClick = () => { @@ -97,7 +97,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic }); }; - menu.Get("SETTINGS").OnClick = () => + var settingsButton = widget.Get("SETTINGS"); + settingsButton.OnClick = () => { hideMenu = true; Ui.OpenWindow("SETTINGS_PANEL", new WidgetArgs() From 8c43ffb802a3c35391b744ed81b5d0e5b72b74ca Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 4 Aug 2014 11:49:56 +0200 Subject: [PATCH 11/13] Add some Media functions to new Lua API --- .../Scripting/Global/MediaGlobal.cs | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.RA/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/MediaGlobal.cs index dad0f4e886..463de6f73d 100644 --- a/OpenRA.Mods.RA/Scripting/Global/MediaGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/MediaGlobal.cs @@ -8,14 +8,58 @@ */ #endregion +using System; using System.Drawing; +using Eluant; +using OpenRA.Scripting; namespace OpenRA.Scripting { [ScriptGlobal("Media")] public class MediaGlobal : ScriptGlobal { - public MediaGlobal(ScriptContext context) : base(context) { } + World world; + public MediaGlobal(ScriptContext context) : base(context) + { + world = context.World; + } + [Desc("Play an announcer voice listed in notifications.yaml")] + public void PlaySpeechNotification(Player player, string notification) + { + Sound.PlayNotification(world.Map.Rules, player, "Speech", notification, player != null ? player.Country.Race : null); + } + + [Desc("Play a sound listed in notifications.yaml")] + public void PlaySoundNotification(Player player, string notification) + { + 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(); + } + catch (LuaException e) + { + context.FatalError(e.Message); + } + }; + } + else + onComplete = () => { }; + + Media.PlayFMVFullscreen(world, movie, onComplete); + } [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 From 00fcf5f969116bff061010530e30038c966fb6ea Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 4 Aug 2014 11:50:21 +0200 Subject: [PATCH 12/13] Convert gdi01 to new Lua --- mods/cnc/maps/gdi01/gdi01.lua | 88 +++++++++++++++++++++-------------- mods/cnc/maps/gdi01/map.yaml | 4 +- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/mods/cnc/maps/gdi01/gdi01.lua b/mods/cnc/maps/gdi01/gdi01.lua index 52fbc5f8e7..b54c5f8e98 100644 --- a/mods/cnc/maps/gdi01/gdi01.lua +++ b/mods/cnc/maps/gdi01/gdi01.lua @@ -2,58 +2,76 @@ InfantryReinforcements = { "e1", "e1", "e1" } VehicleReinforcements = { "jeep" } NodPatrol = { "e1", "e1" } -MissionAccomplished = function() - Mission.MissionOver({ player }, nil, true) - Media.PlayMovieFullscreen("consyard.vqa") -end - -MissionFailed = function() - Mission.MissionOver(nil, { player }, true) - Media.PlayMovieFullscreen("gameover.vqa") -end - SendNodPatrol = function() - local patrol = Reinforcements.Reinforce(enemy, NodPatrol, nod0.Location, nod1.Location, 0) - Utils.Do(patrol, function(soldier) - Actor.Move(soldier, nod2.Location) - Actor.Move(soldier, nod3.Location) - Actor.Hunt(soldier) + Utils.Do(NodPatrol, function(type) + local soldier = Actor.Create(type, true, { Location = nod0.Location, Owner = enemy }) + soldier.Move(nod1.Location) + soldier.AttackMove(nod2.Location) + soldier.Move(nod3.Location) + soldier.Hunt() end) end -SetGunboatPath = function() - Actor.AttackMove(Gunboat, gunboatLeft.Location) - Actor.AttackMove(Gunboat, gunboatRight.Location) +SetGunboatPath = function(gunboat) + gunboat.AttackMove(gunboatLeft.Location) + gunboat.AttackMove(gunboatRight.Location) end ReinforceFromSea = function(passengers) - local hovercraft, troops = Reinforcements.Insert(player, "oldlst", passengers, { lstStart.Location, lstEnd.Location }, { lstStart.Location }) - Media.PlaySpeechNotification("Reinforce") + local transport = Actor.Create("oldlst", true, { Location = lstStart.Location, Owner = player }) + + Utils.Do(passengers, function(actorType) + local passenger = Actor.Create(actorType, false, { Owner = player }) + transport.LoadPassenger(passenger) + end) + + transport.Move(lstEnd.Location) + transport.UnloadPassengers() + transport.Wait(50) + transport.Move(lstStart.Location) + transport.Destroy() + + Media.PlaySpeechNotification(player, "Reinforce") end WorldLoaded = function() - player = OpenRA.GetPlayer("GDI") - enemy = OpenRA.GetPlayer("Nod") - Media.PlayMovieFullscreen("gdi1.vqa", function() Media.PlayMovieFullscreen("landing.vqa") end) + player = Player.GetPlayer("GDI") + enemy = Player.GetPlayer("Nod") + + gdiObjective = player.AddPrimaryObjective("Destroy all Nod forces in the area!") + + Trigger.OnPlayerWon(player, function() + Media.PlaySpeechNotification(player, "Win") + Trigger.AfterDelay(25, function() + Media.PlayMovieFullscreen("consyard.vqa") + end) + end) + + Trigger.OnPlayerLost(player, function() + Media.PlaySpeechNotification(player, "Lose") + Trigger.AfterDelay(25, function() + Media.PlayMovieFullscreen("gameover.vqa") + end) + end) + + Trigger.OnIdle(Gunboat, function() SetGunboatPath(Gunboat) end) + SendNodPatrol() - OpenRA.RunAfterDelay(25 * 5, function() ReinforceFromSea(InfantryReinforcements) end) - OpenRA.RunAfterDelay(25 * 15, function() ReinforceFromSea(InfantryReinforcements) end) - OpenRA.RunAfterDelay(25 * 30, function() ReinforceFromSea(VehicleReinforcements) end) - OpenRA.RunAfterDelay(25 * 60, function() ReinforceFromSea(VehicleReinforcements) end) + Trigger.AfterDelay(25 * 5, function() ReinforceFromSea(InfantryReinforcements) end) + Trigger.AfterDelay(25 * 15, function() ReinforceFromSea(InfantryReinforcements) end) + Trigger.AfterDelay(25 * 30, function() ReinforceFromSea(VehicleReinforcements) end) + Trigger.AfterDelay(25 * 60, function() ReinforceFromSea(VehicleReinforcements) end) end Tick = function() - if Actor.IsIdle(Gunboat) then - SetGunboatPath() + if enemy.HasNoRequiredUnits() then + player.MarkCompletedObjective(gdiObjective) end - if Mission.RequiredUnitsAreDestroyed(player) then - MissionFailed() + if player.HasNoRequiredUnits() then + player.MarkFailedObjective(gdiObjective) end - if Mission.RequiredUnitsAreDestroyed(enemy) then - MissionAccomplished() - end -end \ No newline at end of file +end diff --git a/mods/cnc/maps/gdi01/map.yaml b/mods/cnc/maps/gdi01/map.yaml index 4a82cf473e..c06e937d74 100644 --- a/mods/cnc/maps/gdi01/map.yaml +++ b/mods/cnc/maps/gdi01/map.yaml @@ -457,8 +457,8 @@ Rules: PlayMusicOnMapLoad: Music: aoi Loop: false - LuaScriptInterface: - LuaScripts: gdi01.lua + LuaScript: + Scripts: gdi01.lua ObjectivesPanel: PanelName: MISSION_OBJECTIVES Player: From c9ee475ca93d66c22303650c575e5dd6c4ec1123 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Wed, 6 Aug 2014 13:34:28 +0200 Subject: [PATCH 13/13] Added upgrade rules --- OpenRA.Utility/UpgradeRules.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/OpenRA.Utility/UpgradeRules.cs b/OpenRA.Utility/UpgradeRules.cs index 12d943e97b..c8b6a556e2 100644 --- a/OpenRA.Utility/UpgradeRules.cs +++ b/OpenRA.Utility/UpgradeRules.cs @@ -364,6 +364,22 @@ namespace OpenRA.Utility } } + if (engineVersion < 20140806) + { + // remove ConquestVictoryConditions when StrategicVictoryConditions is set + if (depth == 0 && node.Key == "Player" && node.Value.Nodes.Any(n => n.Key == "StrategicVictoryConditions")) + node.Value.Nodes.Add(new MiniYamlNode("-ConquestVictoryConditions", "")); + + // the objectives panel trait and its properties have been renamed + if (depth == 1 && node.Key == "ConquestObjectivesPanel") + { + node.Key = "ObjectivesPanel"; + node.Value.Nodes.RemoveAll(_ => true); + node.Value.Nodes.Add(new MiniYamlNode("PanelName", new MiniYaml("SKIRMISH_STATS"))); + } + + } + // Veterancy was changed to use the upgrades system if (engineVersion < 20140807) {