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