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.Linq; 13 using OpenRA.Network; 14 using OpenRA.Primitives; 15 using OpenRA.Traits; 16 17 namespace OpenRA.Mods.Common.Traits 18 { 19 public class ConquestVictoryConditionsInfo : ITraitInfo, Requires<MissionObjectivesInfo> 20 { 21 [Desc("Delay for the end game notification in milliseconds.")] 22 public readonly int NotificationDelay = 1500; 23 24 [Translate] 25 [Desc("Description of the objective.")] 26 public readonly string Objective = "Destroy all opposition!"; 27 28 [Desc("Disable the win/loss messages and audio notifications?")] 29 public readonly bool SuppressNotifications = false; 30 Create(ActorInitializer init)31 public object Create(ActorInitializer init) { return new ConquestVictoryConditions(init.Self, this); } 32 } 33 34 public class ConquestVictoryConditions : ITick, INotifyWinStateChanged, INotifyTimeLimit 35 { 36 readonly ConquestVictoryConditionsInfo info; 37 readonly MissionObjectives mo; 38 readonly bool shortGame; 39 Player[] otherPlayers; 40 int objectiveID = -1; 41 ConquestVictoryConditions(Actor self, ConquestVictoryConditionsInfo cvcInfo)42 public ConquestVictoryConditions(Actor self, ConquestVictoryConditionsInfo cvcInfo) 43 { 44 info = cvcInfo; 45 mo = self.Trait<MissionObjectives>(); 46 shortGame = self.Owner.World.WorldActor.Trait<MapOptions>().ShortGame; 47 } 48 ITick.Tick(Actor self)49 void ITick.Tick(Actor self) 50 { 51 if (self.Owner.WinState != WinState.Undefined || self.Owner.NonCombatant) 52 return; 53 54 if (objectiveID < 0) 55 objectiveID = mo.Add(self.Owner, info.Objective, "Primary", inhibitAnnouncement: true); 56 57 if (!self.Owner.NonCombatant && self.Owner.HasNoRequiredUnits(shortGame)) 58 mo.MarkFailed(self.Owner, objectiveID); 59 60 // Players, NonCombatants, and IsAlliedWith are all fixed once the game starts, so we can cache the result. 61 if (otherPlayers == null) 62 otherPlayers = self.World.Players.Where(p => !p.NonCombatant && !p.IsAlliedWith(self.Owner)).ToArray(); 63 64 if (otherPlayers.Length == 0) return; 65 66 // PERF: Avoid LINQ. 67 foreach (var otherPlayer in otherPlayers) 68 if (otherPlayer.WinState != WinState.Lost) 69 return; 70 71 mo.MarkCompleted(self.Owner, objectiveID); 72 } 73 INotifyTimeLimit.NotifyTimerExpired(Actor self)74 void INotifyTimeLimit.NotifyTimerExpired(Actor self) 75 { 76 if (objectiveID < 0) 77 return; 78 79 var myTeam = self.World.LobbyInfo.ClientWithIndex(self.Owner.ClientIndex).Team; 80 var teams = self.World.Players.Where(p => !p.NonCombatant && p.Playable) 81 .Select(p => new Pair<Player, PlayerStatistics>(p, p.PlayerActor.TraitOrDefault<PlayerStatistics>())) 82 .OrderByDescending(p => p.Second != null ? p.Second.Experience : 0) 83 .GroupBy(p => (self.World.LobbyInfo.ClientWithIndex(p.First.ClientIndex) ?? new Session.Client()).Team) 84 .OrderByDescending(g => g.Sum(gg => gg.Second != null ? gg.Second.Experience : 0)); 85 86 if (teams.First().Key == myTeam && (myTeam != 0 || teams.First().First().First == self.Owner)) 87 { 88 mo.MarkCompleted(self.Owner, objectiveID); 89 return; 90 } 91 92 mo.MarkFailed(self.Owner, objectiveID); 93 } 94 INotifyWinStateChanged.OnPlayerLost(Player player)95 void INotifyWinStateChanged.OnPlayerLost(Player player) 96 { 97 foreach (var a in player.World.ActorsWithTrait<INotifyOwnerLost>().Where(a => a.Actor.Owner == player)) 98 a.Trait.OnOwnerLost(a.Actor); 99 100 if (info.SuppressNotifications) 101 return; 102 103 Game.AddSystemLine(player.PlayerName + " is defeated."); 104 Game.RunAfterDelay(info.NotificationDelay, () => 105 { 106 if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer) 107 Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", mo.Info.LoseNotification, player.Faction.InternalName); 108 }); 109 } 110 INotifyWinStateChanged.OnPlayerWon(Player player)111 void INotifyWinStateChanged.OnPlayerWon(Player player) 112 { 113 if (info.SuppressNotifications) 114 return; 115 116 Game.AddSystemLine(player.PlayerName + " is victorious."); 117 Game.RunAfterDelay(info.NotificationDelay, () => 118 { 119 if (Game.IsCurrentWorld(player.World) && player == player.World.LocalPlayer) 120 Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", mo.Info.WinNotification, player.Faction.InternalName); 121 }); 122 } 123 } 124 } 125