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