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;
13 using System.Collections.Generic;
14 using System.Linq;
15 using Eluant;
16 using Eluant.ObjectBinding;
17 using OpenRA.Graphics;
18 using OpenRA.Network;
19 using OpenRA.Primitives;
20 using OpenRA.Scripting;
21 using OpenRA.Traits;
22 using OpenRA.Widgets;
23 
24 namespace OpenRA
25 {
26 	[Flags]
27 	public enum PowerState
28 	{
29 		Normal = 1,
30 		Low = 2,
31 		Critical = 4
32 	}
33 
34 	public enum WinState { Undefined, Won, Lost }
35 
36 	public class PlayerBitMask { }
37 
38 	public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
39 	{
40 		struct StanceColors
41 		{
42 			public Color Self;
43 			public Color Allies;
44 			public Color Enemies;
45 			public Color Neutrals;
46 		}
47 
48 		public readonly Actor PlayerActor;
49 		public readonly Color Color;
50 
51 		public readonly string PlayerName;
52 		public readonly string InternalName;
53 		public readonly FactionInfo Faction;
54 		public readonly bool NonCombatant = false;
55 		public readonly bool Playable = true;
56 		public readonly int ClientIndex;
57 		public readonly PlayerReference PlayerReference;
58 		public readonly bool IsBot;
59 		public readonly string BotType;
60 		public readonly Shroud Shroud;
61 		public readonly FrozenActorLayer FrozenActorLayer;
62 
63 		/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
64 		public readonly FactionInfo DisplayFaction;
65 
66 		public WinState WinState = WinState.Undefined;
67 		public int SpawnPoint;
68 		public bool HasObjectives = false;
69 		public bool Spectating;
70 
71 		public World World { get; private set; }
72 
73 		readonly bool inMissionMap;
74 		readonly IUnlocksRenderPlayer[] unlockRenderPlayer;
75 
76 		// Each player is identified with a unique bit in the set
77 		// Cache masks for the player's index and ally/enemy player indices for performance.
78 		public LongBitSet<PlayerBitMask> PlayerMask;
79 		public LongBitSet<PlayerBitMask> AlliedPlayersMask = default(LongBitSet<PlayerBitMask>);
80 		public LongBitSet<PlayerBitMask> EnemyPlayersMask = default(LongBitSet<PlayerBitMask>);
81 
82 		public bool UnlockedRenderPlayer
83 		{
84 			get
85 			{
86 				if (unlockRenderPlayer.Any(x => x.RenderPlayerUnlocked))
87 					return true;
88 
89 				return WinState != WinState.Undefined && !inMissionMap;
90 			}
91 		}
92 
93 		readonly StanceColors stanceColors;
94 
ChooseFaction(World world, string name, bool requireSelectable = true)95 		static FactionInfo ChooseFaction(World world, string name, bool requireSelectable = true)
96 		{
97 			var selectableFactions = world.Map.Rules.Actors["world"].TraitInfos<FactionInfo>()
98 				.Where(f => !requireSelectable || f.Selectable)
99 				.ToList();
100 
101 			var selected = selectableFactions.FirstOrDefault(f => f.InternalName == name)
102 				?? selectableFactions.Random(world.SharedRandom);
103 
104 			// Don't loop infinite
105 			for (var i = 0; i <= 10 && selected.RandomFactionMembers.Any(); i++)
106 			{
107 				var faction = selected.RandomFactionMembers.Random(world.SharedRandom);
108 				selected = selectableFactions.FirstOrDefault(f => f.InternalName == faction);
109 
110 				if (selected == null)
111 					throw new YamlException("Unknown faction: {0}".F(faction));
112 			}
113 
114 			return selected;
115 		}
116 
ChooseDisplayFaction(World world, string factionName)117 		static FactionInfo ChooseDisplayFaction(World world, string factionName)
118 		{
119 			var factions = world.Map.Rules.Actors["world"].TraitInfos<FactionInfo>().ToArray();
120 
121 			return factions.FirstOrDefault(f => f.InternalName == factionName) ?? factions.First();
122 		}
123 
Player(World world, Session.Client client, PlayerReference pr)124 		public Player(World world, Session.Client client, PlayerReference pr)
125 		{
126 			World = world;
127 			InternalName = pr.Name;
128 			PlayerReference = pr;
129 
130 			inMissionMap = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector);
131 
132 			// Real player or host-created bot
133 			if (client != null)
134 			{
135 				ClientIndex = client.Index;
136 				Color = client.Color;
137 				if (client.Bot != null)
138 				{
139 					var botInfo = world.Map.Rules.Actors["player"].TraitInfos<IBotInfo>().First(b => b.Type == client.Bot);
140 					var botsOfSameType = world.LobbyInfo.Clients.Where(c => c.Bot == client.Bot).ToArray();
141 					PlayerName = botsOfSameType.Length == 1 ? botInfo.Name : "{0} {1}".F(botInfo.Name, botsOfSameType.IndexOf(client) + 1);
142 				}
143 				else
144 					PlayerName = client.Name;
145 
146 				BotType = client.Bot;
147 				Faction = ChooseFaction(world, client.Faction, !pr.LockFaction);
148 				DisplayFaction = ChooseDisplayFaction(world, client.Faction);
149 			}
150 			else
151 			{
152 				// Map player
153 				ClientIndex = 0; // Owned by the host (TODO: fix this)
154 				Color = pr.Color;
155 				PlayerName = pr.Name;
156 				NonCombatant = pr.NonCombatant;
157 				Playable = pr.Playable;
158 				Spectating = pr.Spectating;
159 				BotType = pr.Bot;
160 				Faction = ChooseFaction(world, pr.Faction, false);
161 				DisplayFaction = ChooseDisplayFaction(world, pr.Faction);
162 			}
163 
164 			if (!Spectating)
165 				PlayerMask = new LongBitSet<PlayerBitMask>(InternalName);
166 
167 			var playerActorType = world.Type == WorldType.Editor ? "EditorPlayer" : "Player";
168 			PlayerActor = world.CreateActor(playerActorType, new TypeDictionary { new OwnerInit(this) });
169 			Shroud = PlayerActor.Trait<Shroud>();
170 			FrozenActorLayer = PlayerActor.TraitOrDefault<FrozenActorLayer>();
171 
172 			// Enable the bot logic on the host
173 			IsBot = BotType != null;
174 			if (IsBot && Game.IsHost)
175 			{
176 				var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Type == BotType);
177 				if (logic == null)
178 					Log.Write("debug", "Invalid bot type: {0}", BotType);
179 				else
180 					logic.Activate(this);
181 			}
182 
183 			stanceColors.Self = ChromeMetrics.Get<Color>("PlayerStanceColorSelf");
184 			stanceColors.Allies = ChromeMetrics.Get<Color>("PlayerStanceColorAllies");
185 			stanceColors.Enemies = ChromeMetrics.Get<Color>("PlayerStanceColorEnemies");
186 			stanceColors.Neutrals = ChromeMetrics.Get<Color>("PlayerStanceColorNeutrals");
187 
188 			unlockRenderPlayer = PlayerActor.TraitsImplementing<IUnlocksRenderPlayer>().ToArray();
189 		}
190 
ToString()191 		public override string ToString()
192 		{
193 			return "{0} ({1})".F(PlayerName, ClientIndex);
194 		}
195 
196 		public Dictionary<Player, Stance> Stances = new Dictionary<Player, Stance>();
IsAlliedWith(Player p)197 		public bool IsAlliedWith(Player p)
198 		{
199 			// Observers are considered allies to active combatants
200 			return p == null || Stances[p] == Stance.Ally || (p.Spectating && !NonCombatant);
201 		}
202 
PlayerStanceColor(Actor a)203 		public Color PlayerStanceColor(Actor a)
204 		{
205 			var player = a.World.RenderPlayer ?? a.World.LocalPlayer;
206 			if (player != null && !player.Spectating)
207 			{
208 				var apparentOwner = a.EffectiveOwner != null && a.EffectiveOwner.Disguised
209 					? a.EffectiveOwner.Owner
210 					: a.Owner;
211 
212 				if (a.Owner.IsAlliedWith(a.World.RenderPlayer))
213 					apparentOwner = a.Owner;
214 
215 				if (apparentOwner == player)
216 					return stanceColors.Self;
217 
218 				if (apparentOwner.IsAlliedWith(player))
219 					return stanceColors.Allies;
220 
221 				if (!apparentOwner.NonCombatant)
222 					return stanceColors.Enemies;
223 			}
224 
225 			return stanceColors.Neutrals;
226 		}
227 
228 		#region Scripting interface
229 
230 		Lazy<ScriptPlayerInterface> luaInterface;
OnScriptBind(ScriptContext context)231 		public void OnScriptBind(ScriptContext context)
232 		{
233 			if (luaInterface == null)
234 				luaInterface = Exts.Lazy(() => new ScriptPlayerInterface(context, this));
235 		}
236 
237 		public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
238 		{
239 			get { return luaInterface.Value[runtime, keyValue]; }
240 			set { luaInterface.Value[runtime, keyValue] = value; }
241 		}
242 
Equals(LuaRuntime runtime, LuaValue left, LuaValue right)243 		public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
244 		{
245 			Player a, b;
246 			if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
247 				return false;
248 
249 			return a == b;
250 		}
251 
ToString(LuaRuntime runtime)252 		public LuaValue ToString(LuaRuntime runtime)
253 		{
254 			return "Player ({0})".F(PlayerName);
255 		}
256 
257 		#endregion
258 	}
259 }
260