From 6e3000ab0e2aa8eaf86ecbe38818c0b57f3d962f Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Fri, 25 Jul 2014 09:25:32 +0200 Subject: [PATCH] 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