1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System.Collections.Generic; 13 using System.Linq; 14 using OpenRA.Network; 15 using OpenRA.Primitives; 16 using OpenRA.Traits; 17 18 namespace OpenRA.Mods.Common.Traits 19 { 20 [Desc("Used to mark a place that needs to be in possession for StrategicVictoryConditions.")] 21 public class StrategicPointInfo : TraitInfo<StrategicPoint> { } 22 public class StrategicPoint { } 23 24 [Desc("Allows King of the Hill (KotH) style gameplay.")] 25 public class StrategicVictoryConditionsInfo : ITraitInfo, Requires<MissionObjectivesInfo> 26 { 27 [Desc("Amount of time (in game ticks) that the player has to hold all the strategic points.", "Defaults to 7500 ticks (5 minutes at default speed).")] 28 public readonly int HoldDuration = 7500; 29 30 [Desc("Should the timer reset when the player loses hold of a strategic point.")] 31 public readonly bool ResetOnHoldLost = true; 32 33 [Desc("Percentage of all strategic points the player has to hold to win.")] 34 public readonly int RatioRequired = 50; 35 36 [Desc("Delay for the end game notification in milliseconds.")] 37 public readonly int NotificationDelay = 1500; 38 39 [Translate] 40 [Desc("Description of the objective")] 41 public readonly string Objective = "Hold all the strategic positions!"; 42 43 [Desc("Disable the win/loss messages and audio notifications?")] 44 public readonly bool SuppressNotifications = false; 45 Create(ActorInitializer init)46 public object Create(ActorInitializer init) { return new StrategicVictoryConditions(init.Self, this); } 47 } 48 49 public class StrategicVictoryConditions : ITick, ISync, INotifyWinStateChanged, INotifyTimeLimit 50 { 51 readonly StrategicVictoryConditionsInfo info; 52 53 [Sync] 54 public int TicksLeft; 55 56 readonly Player player; 57 readonly MissionObjectives mo; 58 readonly bool shortGame; 59 int objectiveID = -1; 60 StrategicVictoryConditions(Actor self, StrategicVictoryConditionsInfo svcInfo)61 public StrategicVictoryConditions(Actor self, StrategicVictoryConditionsInfo svcInfo) 62 { 63 info = svcInfo; 64 TicksLeft = info.HoldDuration; 65 player = self.Owner; 66 mo = self.Trait<MissionObjectives>(); 67 shortGame = player.World.WorldActor.Trait<MapOptions>().ShortGame; 68 } 69 70 public IEnumerable<Actor> AllPoints 71 { 72 get { return player.World.ActorsHavingTrait<StrategicPoint>(); } 73 } 74 75 public int Total { get { return AllPoints.Count(); } } 76 int Owned { get { return AllPoints.Count(a => WorldUtils.AreMutualAllies(player, a.Owner)); } } 77 78 public bool Holding { get { return Owned >= info.RatioRequired * Total / 100; } } 79 ITick.Tick(Actor self)80 void ITick.Tick(Actor self) 81 { 82 if (player.WinState != WinState.Undefined || player.NonCombatant) 83 return; 84 85 if (objectiveID < 0) 86 objectiveID = mo.Add(player, info.Objective, "Primary", inhibitAnnouncement: true); 87 88 if (!self.Owner.NonCombatant && self.Owner.HasNoRequiredUnits(shortGame)) 89 mo.MarkFailed(self.Owner, objectiveID); 90 91 var others = self.World.Players.Where(p => !p.NonCombatant 92 && !p.IsAlliedWith(self.Owner)); 93 94 if (others.All(p => p.WinState == WinState.Lost)) 95 mo.MarkCompleted(player, objectiveID); 96 97 if (others.Any(p => p.WinState == WinState.Won)) 98 mo.MarkFailed(player, objectiveID); 99 100 // See if any of the conditions are met to increase the count 101 if (Total > 0) 102 { 103 if (Holding) 104 { 105 // Hah! We met this critical owned condition 106 if (--TicksLeft == 0) 107 mo.MarkCompleted(player, objectiveID); 108 } 109 else if (TicksLeft != 0) 110 if (info.ResetOnHoldLost) 111 TicksLeft = info.HoldDuration; // Reset the time hold 112 } 113 } 114 INotifyTimeLimit.NotifyTimerExpired(Actor self)115 void INotifyTimeLimit.NotifyTimerExpired(Actor self) 116 { 117 if (objectiveID < 0) 118 return; 119 120 var myTeam = self.World.LobbyInfo.ClientWithIndex(self.Owner.ClientIndex).Team; 121 var teams = self.World.Players.Where(p => !p.NonCombatant && p.Playable) 122 .Select(p => new Pair<Player, PlayerStatistics>(p, p.PlayerActor.TraitOrDefault<PlayerStatistics>())) 123 .OrderByDescending(p => p.Second != null ? p.Second.Experience : 0) 124 .GroupBy(p => (self.World.LobbyInfo.ClientWithIndex(p.First.ClientIndex) ?? new Session.Client()).Team) 125 .OrderByDescending(g => g.Sum(gg => gg.Second != null ? gg.Second.Experience : 0)); 126 127 if (teams.First().Key == myTeam && (myTeam != 0 || teams.First().First().First == self.Owner)) 128 { 129 mo.MarkCompleted(self.Owner, objectiveID); 130 return; 131 } 132 133 mo.MarkFailed(self.Owner, objectiveID); 134 } 135 INotifyWinStateChanged.OnPlayerLost(Player player)136 void INotifyWinStateChanged.OnPlayerLost(Player player) 137 { 138 foreach (var a in player.World.ActorsWithTrait<INotifyOwnerLost>().Where(a => a.Actor.Owner == player)) 139 a.Trait.OnOwnerLost(a.Actor); 140 141 if (info.SuppressNotifications) 142 return; 143 144 Game.AddSystemLine(player.PlayerName + " is defeated."); 145 Game.RunAfterDelay(info.NotificationDelay, () => 146 { 147 if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer) 148 Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", mo.Info.LoseNotification, player.Faction.InternalName); 149 }); 150 } 151 INotifyWinStateChanged.OnPlayerWon(Player player)152 void INotifyWinStateChanged.OnPlayerWon(Player player) 153 { 154 if (info.SuppressNotifications) 155 return; 156 157 Game.AddSystemLine(player.PlayerName + " is victorious."); 158 Game.RunAfterDelay(info.NotificationDelay, () => 159 { 160 if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer) 161 Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", mo.Info.WinNotification, player.Faction.InternalName); 162 }); 163 } 164 } 165 } 166