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