Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
337
OpenRA.Game/Player.cs
Normal file
337
OpenRA.Game/Player.cs
Normal file
@@ -0,0 +1,337 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
public enum PowerState
|
||||
{
|
||||
Normal = 1,
|
||||
Low = 2,
|
||||
Critical = 4
|
||||
}
|
||||
|
||||
public enum WinState { Undefined, Won, Lost }
|
||||
|
||||
public class PlayerBitMask { }
|
||||
|
||||
public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
|
||||
{
|
||||
[FluentReference("name", "number")]
|
||||
const string EnumeratedBotName = "enumerated-bot-name";
|
||||
|
||||
public readonly Actor PlayerActor;
|
||||
public readonly string PlayerName;
|
||||
public readonly string InternalName;
|
||||
public readonly FactionInfo Faction;
|
||||
public readonly bool NonCombatant = false;
|
||||
public readonly bool Playable = true;
|
||||
public readonly int ClientIndex;
|
||||
public readonly CPos HomeLocation;
|
||||
public readonly int Handicap;
|
||||
public readonly PlayerReference PlayerReference;
|
||||
public readonly bool IsBot;
|
||||
public readonly string BotType;
|
||||
public readonly Shroud Shroud;
|
||||
public readonly FrozenActorLayer FrozenActorLayer;
|
||||
|
||||
readonly Color color;
|
||||
|
||||
/// <summary>Returns player color with relationship colors applied.</summary>
|
||||
public Color Color { get; private set; }
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public readonly FactionInfo DisplayFaction;
|
||||
|
||||
/// <summary>The spawn point index that was assigned for client-based players.</summary>
|
||||
public readonly int SpawnPoint;
|
||||
|
||||
/// <summary>The spawn point index (including 0 for Random) that was selected in the lobby for client-based players.</summary>
|
||||
public readonly int DisplaySpawnPoint;
|
||||
|
||||
public WinState WinState = WinState.Undefined;
|
||||
public bool HasObjectives = false;
|
||||
|
||||
// Players in mission maps must not leave the player view
|
||||
public bool Spectating => !inMissionMap && (spectating || WinState != WinState.Undefined);
|
||||
|
||||
public World World { get; }
|
||||
|
||||
readonly bool inMissionMap;
|
||||
readonly bool spectating;
|
||||
readonly IUnlocksRenderPlayer[] unlockRenderPlayer;
|
||||
readonly INotifyPlayerDisconnected[] notifyDisconnected;
|
||||
|
||||
readonly IReadOnlyCollection<IBotInfo> botInfos;
|
||||
string resolvedPlayerName;
|
||||
|
||||
// Each player is identified with a unique bit in the set
|
||||
// Cache masks for the player's index and ally/enemy player indices for performance.
|
||||
public LongBitSet<PlayerBitMask> PlayerMask;
|
||||
public LongBitSet<PlayerBitMask> AlliedPlayersMask = default;
|
||||
public LongBitSet<PlayerBitMask> EnemyPlayersMask = default;
|
||||
|
||||
public bool UnlockedRenderPlayer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (unlockRenderPlayer.Any(x => x.RenderPlayerUnlocked))
|
||||
return true;
|
||||
|
||||
return WinState != WinState.Undefined && !inMissionMap;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The chosen player name including localized and enumerated bot names.</summary>
|
||||
public string ResolvedPlayerName
|
||||
{
|
||||
get
|
||||
{
|
||||
resolvedPlayerName ??= ResolvePlayerName();
|
||||
return resolvedPlayerName;
|
||||
}
|
||||
}
|
||||
|
||||
public static FactionInfo ResolveFaction(
|
||||
string factionName, IEnumerable<FactionInfo> factionInfos, MersenneTwister playerRandom, bool requireSelectable = true)
|
||||
{
|
||||
var selectableFactions = factionInfos
|
||||
.Where(f => !requireSelectable || f.Selectable)
|
||||
.ToList();
|
||||
|
||||
var selected = selectableFactions.FirstOrDefault(f => f.InternalName == factionName)
|
||||
?? selectableFactions.Random(playerRandom);
|
||||
|
||||
// Don't loop infinite
|
||||
for (var i = 0; i <= 10 && selected.RandomFactionMembers.Count > 0; i++)
|
||||
{
|
||||
var faction = selected.RandomFactionMembers.Random(playerRandom);
|
||||
selected = selectableFactions.FirstOrDefault(f => f.InternalName == faction);
|
||||
|
||||
if (selected == null)
|
||||
throw new YamlException($"Unknown faction: {faction}");
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
static FactionInfo ResolveFaction(World world, string factionName, MersenneTwister playerRandom, bool requireSelectable)
|
||||
{
|
||||
var factionInfos = world.Map.Rules.Actors[SystemActors.World].TraitInfos<FactionInfo>();
|
||||
return ResolveFaction(factionName, factionInfos, playerRandom, requireSelectable);
|
||||
}
|
||||
|
||||
static FactionInfo ResolveDisplayFaction(World world, string factionName)
|
||||
{
|
||||
var factions = world.Map.Rules.Actors[SystemActors.World].TraitInfos<FactionInfo>();
|
||||
|
||||
return factions.FirstOrDefault(f => f.InternalName == factionName) ?? factions.First();
|
||||
}
|
||||
|
||||
public Player(World world, Session.Client client, PlayerReference pr, MersenneTwister playerRandom)
|
||||
{
|
||||
World = world;
|
||||
InternalName = pr.Name;
|
||||
PlayerReference = pr;
|
||||
|
||||
inMissionMap = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector);
|
||||
botInfos = World.Map.Rules.Actors[SystemActors.Player].TraitInfos<IBotInfo>();
|
||||
|
||||
// Real player or host-created bot
|
||||
if (client != null)
|
||||
{
|
||||
ClientIndex = client.Index;
|
||||
color = client.Color;
|
||||
Color = color;
|
||||
PlayerName = client.Name;
|
||||
|
||||
BotType = client.Bot;
|
||||
Faction = ResolveFaction(world, client.Faction, playerRandom, !pr.LockFaction);
|
||||
DisplayFaction = ResolveDisplayFaction(world, client.Faction);
|
||||
|
||||
var assignSpawnPoints = world.WorldActor.TraitOrDefault<IAssignSpawnPoints>();
|
||||
HomeLocation = assignSpawnPoints?.AssignHomeLocation(world, client, playerRandom) ?? pr.HomeLocation;
|
||||
SpawnPoint = assignSpawnPoints?.SpawnPointForPlayer(this) ?? client.SpawnPoint;
|
||||
DisplaySpawnPoint = client.SpawnPoint;
|
||||
|
||||
Handicap = client.Handicap;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Map player
|
||||
ClientIndex = world.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin)?.Index ?? 0; // Owned by the host (TODO: fix this)
|
||||
color = pr.Color;
|
||||
Color = pr.Color;
|
||||
PlayerName = pr.Name;
|
||||
NonCombatant = pr.NonCombatant;
|
||||
Playable = pr.Playable;
|
||||
spectating = pr.Spectating;
|
||||
BotType = pr.Bot;
|
||||
Faction = ResolveFaction(world, pr.Faction, playerRandom, false);
|
||||
DisplayFaction = ResolveDisplayFaction(world, pr.Faction);
|
||||
HomeLocation = pr.HomeLocation;
|
||||
SpawnPoint = DisplaySpawnPoint = 0;
|
||||
Handicap = pr.Handicap;
|
||||
}
|
||||
|
||||
if (!spectating)
|
||||
PlayerMask = new LongBitSet<PlayerBitMask>(InternalName);
|
||||
|
||||
// Set this property before running any Created callbacks on the player actor
|
||||
IsBot = BotType != null;
|
||||
|
||||
// Special case handling is required for the Player actor:
|
||||
// Since Actor.Created would be called before PlayerActor is assigned here
|
||||
// querying player traits in INotifyCreated.Created would crash.
|
||||
// Therefore assign the uninitialized actor and run the Created callbacks
|
||||
// by calling Initialize ourselves.
|
||||
var playerActorType = world.Type == WorldType.Editor ? SystemActors.EditorPlayer : SystemActors.Player;
|
||||
PlayerActor = new Actor(world, playerActorType.ToString(), [new OwnerInit(this)]);
|
||||
PlayerActor.Initialize(true);
|
||||
|
||||
Shroud = PlayerActor.Trait<Shroud>();
|
||||
FrozenActorLayer = PlayerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
|
||||
// Enable the bot logic on the host
|
||||
if (IsBot && Game.IsHost)
|
||||
{
|
||||
var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Type == BotType);
|
||||
if (logic == null)
|
||||
Log.Write("debug", $"Invalid bot type: {BotType}");
|
||||
else
|
||||
logic.Activate(this);
|
||||
}
|
||||
|
||||
unlockRenderPlayer = PlayerActor.TraitsImplementing<IUnlocksRenderPlayer>().ToArray();
|
||||
notifyDisconnected = PlayerActor.TraitsImplementing<INotifyPlayerDisconnected>().ToArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ResolvedPlayerName} ({ClientIndex})";
|
||||
}
|
||||
|
||||
string ResolvePlayerName()
|
||||
{
|
||||
if (IsBot)
|
||||
{
|
||||
var botInfo = botInfos.First(b => b.Type == BotType);
|
||||
var botsOfSameType = World.Players.Where(c => c.BotType == BotType).ToArray();
|
||||
return FluentProvider.GetMessage(EnumeratedBotName,
|
||||
"name", FluentProvider.GetMessage(botInfo.Name),
|
||||
"number", botsOfSameType.IndexOf(this) + 1);
|
||||
}
|
||||
|
||||
return PlayerName;
|
||||
}
|
||||
|
||||
public PlayerRelationship RelationshipWith(Player other)
|
||||
{
|
||||
if (this == other)
|
||||
return PlayerRelationship.Ally;
|
||||
|
||||
// Observers are considered allies to active combatants
|
||||
if (other == null || other.Spectating)
|
||||
return NonCombatant ? PlayerRelationship.Neutral : PlayerRelationship.Ally;
|
||||
|
||||
if (AlliedPlayersMask.Overlaps(other.PlayerMask))
|
||||
return PlayerRelationship.Ally;
|
||||
|
||||
if (EnemyPlayersMask.Overlaps(other.PlayerMask))
|
||||
return PlayerRelationship.Enemy;
|
||||
|
||||
return PlayerRelationship.Neutral;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if player is null.</summary>
|
||||
public bool IsAlliedWith(Player p)
|
||||
{
|
||||
return RelationshipWith(p) == PlayerRelationship.Ally;
|
||||
}
|
||||
|
||||
/// <summary>Returns <see cref="color"/>, ignoring player relationship colors.</summary>
|
||||
public static Color GetColor(Player p) => p.color;
|
||||
|
||||
public static void SetupRelationshipColors(Player[] players, Player viewer, WorldRenderer worldRenderer, bool firstRun)
|
||||
{
|
||||
foreach (var p in players)
|
||||
{
|
||||
p.Color = PlayerRelationshipColor(p, viewer);
|
||||
worldRenderer.UpdatePalettesForPlayer(p.InternalName, p.Color, !firstRun);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color PlayerRelationshipColor(Player player, Player viewer)
|
||||
{
|
||||
if (!Game.Settings.Game.UsePlayerStanceColors || viewer == null || viewer.Spectating)
|
||||
return player.color;
|
||||
|
||||
if (viewer == player)
|
||||
return ChromeMetrics.Get<Color>("PlayerStanceColorSelf");
|
||||
|
||||
if (player.IsAlliedWith(viewer))
|
||||
return ChromeMetrics.Get<Color>("PlayerStanceColorAllies");
|
||||
|
||||
if (player.NonCombatant)
|
||||
return ChromeMetrics.Get<Color>("PlayerStanceColorNeutrals");
|
||||
|
||||
return ChromeMetrics.Get<Color>("PlayerStanceColorEnemies");
|
||||
}
|
||||
|
||||
internal void PlayerDisconnected(Player p)
|
||||
{
|
||||
foreach (var np in notifyDisconnected)
|
||||
np.PlayerDisconnected(PlayerActor, p);
|
||||
}
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptPlayerInterface> luaInterface;
|
||||
public void OnScriptBind(ScriptContext context)
|
||||
{
|
||||
luaInterface ??= Exts.Lazy(() => new ScriptPlayerInterface(context, this));
|
||||
}
|
||||
|
||||
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
|
||||
{
|
||||
get => luaInterface.Value[runtime, keyValue];
|
||||
set => luaInterface.Value[runtime, keyValue] = value;
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out Player a) || !right.TryGetClrValue(out Player b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public LuaValue ToString(LuaRuntime runtime)
|
||||
{
|
||||
return $"Player ({ResolvedPlayerName})";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user