using System; using Gamecraft.Wires; using RobocraftX.Character; using RobocraftX.Character.Movement; using Unity.Mathematics; using RobocraftX.Common; using RobocraftX.Common.Players; using RobocraftX.GUI.Wires; using RobocraftX.Physics; using Svelto.ECS; using Techblox.BuildingDrone; using Techblox.Camera; using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Utility; using UnityEngine; namespace TechbloxModdingAPI { /// /// An in-game player character. Any Leo you see is a player. /// public partial class Player : EcsObjectBase, IEquatable, IEquatable { // static functionality private static readonly PlayerEngine playerEngine = new PlayerEngine(); private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine(); private static Player localPlayer; /// /// Checks if the specified player exists. /// /// Whether the player exists. /// Player type. public static bool Exists(PlayerType player) { switch (player) { case PlayerType.Remote: return playerEngine.GetRemotePlayer() != uint.MaxValue; case PlayerType.Local: return playerEngine.GetLocalPlayer() != uint.MaxValue; } return false; } /// /// Checks if the specified player exists. /// /// Whether the player exists. /// The player's unique identifier. public static bool Exists(uint player) { return playerEngine.ExistsById(player); } /// /// The amount of Players in the current game. /// /// The count. public static uint Count() { return (uint) playerEngine.GetAllPlayerCount(); } /// /// Returns the current player belonging to this client. /// public static Player LocalPlayer { get { if (localPlayer == null || localPlayer.Id != playerEngine.GetLocalPlayer()) localPlayer = new Player(PlayerType.Local); return localPlayer; } } internal static Player GetInstance(uint id) { return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup), e => new Player(e.entityID)); } /// /// Initializes a new instance of the class. /// /// The player's unique identifier. public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup)) { this.Id = id; if (!Exists(id)) { throw new PlayerNotFoundException($"No player with id {id} exists"); } this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote; } /// /// Initializes a new instance of the class. /// /// The player type. Chooses the first available player matching the criteria. public Player(PlayerType player) : base(ecs => { uint id; switch (player) { case PlayerType.Local: id = playerEngine.GetLocalPlayer(); break; case PlayerType.Remote: id = playerEngine.GetRemotePlayer(); break; default: id = uint.MaxValue; break; } if (id == uint.MaxValue) { throw new PlayerNotFoundException($"No player of {player} type exists"); } return new EGID(id, CharacterExclusiveGroups.OnFootGroup); }) { this.Type = player; Id = base.Id.entityID; } // object fields & properties /// /// The player's type. /// The player type is always relative to the current client, not the game host. /// /// The enumerated player type. public PlayerType Type { get; } /// /// The player's unique identifier. /// /// The identifier. public new uint Id { get; } /// /// The player's current position. /// /// The position. public float3 Position { get => playerEngine.GetCharacterStruct(Id).Get().position; set => playerEngine.SetLocation(Id, value, false); } /// /// The player's current rotation. /// /// The rotation. public float3 Rotation { get => ((Quaternion) (GameState.IsBuildMode() ? playerEngine.GetCameraStruct(Id).Get().rotation : playerEngine.GetCharacterStruct(Id).Get().rotation)).eulerAngles; set => _ = GameState.IsBuildMode() ? playerEngine.GetCameraStruct(Id).Get().rotation = quaternion.Euler(value) : playerEngine.GetCharacterStruct(Id).Get().rotation = quaternion.Euler(value); } /// /// The player's current velocity. /// /// The velocity. public float3 Velocity { get => playerEngine.GetCharacterStruct(Id).Get().velocity; set => playerEngine.GetCharacterStruct(Id).Get().velocity = value; } /// /// The player's current angular velocity. /// /// The angular velocity. public float3 AngularVelocity { get => playerEngine.GetCharacterStruct(Id).Get().angularVelocity; set => playerEngine.GetCharacterStruct(Id).Get().angularVelocity = value; } /// /// The player's mass. /// /// The mass. public float Mass => 1f / playerEngine.GetCharacterStruct(Id).Get().physicsMass.InverseMass; /// /// The player's latest network ping time. /// /// The ping (s). public float Ping { get { return playerEngine.GetPing() / 1000f; } } /// /// The player's initial health when entering Simulation (aka Time Running) mode. /// /// The initial health. public float InitialHealth { get { var opt = playerEngine.GetCharacterStruct(Id); return opt ? opt.Get().initialHealth : -1f; } set => playerEngine.GetCharacterStruct(Id).Get().initialHealth = value; } /// /// The player's current health in Simulation (aka Time Running) mode. /// /// The current health. public float CurrentHealth { get { var opt = playerEngine.GetCharacterStruct(Id); return opt ? opt.Get().currentHealth : -1f; } set => playerEngine.GetCharacterStruct(Id).Get().currentHealth = value; } /// /// Whether this is damageable. /// /// true if damageable; otherwise, false. public bool Damageable { get { var opt = playerEngine.GetCharacterStruct(Id); return opt.Get().canTakeDamageStat; } set { ref var healthStruct = ref playerEngine.GetCharacterStruct(Id).Get(); healthStruct.canTakeDamage = value; healthStruct.canTakeDamageStat = value; } } /// /// The player's lives when initially entering Simulation (aka Time Running) mode. /// /// The initial lives. public uint InitialLives { get { var opt = playerEngine.GetCharacterStruct(Id); return opt ? opt.Get().initialLives : uint.MaxValue; } set => playerEngine.GetCharacterStruct(Id).Get().initialLives = value; } /// /// The player's current lives in Simulation (aka Time Running) mode. /// /// The current lives. public uint CurrentLives { get { var opt = playerEngine.GetCharacterStruct(Id); return opt ? opt.Get().currentLives : uint.MaxValue; } set => playerEngine.GetCharacterStruct(Id).Get().currentLives = value; } /*/// /// Whether the Game Over screen is displayed for the player. /// /// true if game over; otherwise, false. public bool GameOver { get => playerEngine.GetGameOverScreen(Id); }*/ /// /// Whether the player is dead. /// If true, hopefully it was quick. /// /// true if dead; otherwise, false. public bool Dead { get => playerEngine.IsDead(Id); } /// /// The player's selected block ID in their hand. /// /// The selected block. public BlockIDs SelectedBlock { get { var optstruct = playerEngine.GetCharacterStruct(Id); return optstruct ? (BlockIDs) optstruct.Get().selectedDBPartID : BlockIDs.Invalid; } } /// /// The player's selected block color in their hand. /// /// The selected block's color. public BlockColor SelectedColor { get { var optstruct = playerEngine.GetCharacterStruct(Id); return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default; } } /// /// The player's selected block colour in their hand. /// /// The selected block's colour. public BlockColor SelectedColour { get { var optstruct = playerEngine.GetCharacterStruct(Id); return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default; } } /// /// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage. /// public Blueprint SelectedBlueprint { get { var lbiso = playerEngine.GetPlayerStruct(Id, Type); return lbiso ? new Blueprint(lbiso.Get().selectedBlueprintId) : null; } set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue); } /// /// The player's mode in time stopped mode, determining what they place. /// public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine .GetCharacterStruct(Id).Get().timeStoppedContext, 2); // It's a bit field in game now /// /// Whether the player is sprinting. /// public bool Sprinting { get => GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().sprinting : playerEngine.GetCharacterStruct(Id).Get().isSprinting; set => _ = GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().sprinting = value : playerEngine.GetCharacterStruct(Id).Get().isSprinting = value; } /// /// Movement speed setting. Build mode (camera) and simulation mode settings are separate. /// public float SpeedSetting { get => GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().speed : playerEngine.GetCharacterStruct(Id).Get().moveSpeed; set => _ = GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().speed = value : playerEngine.GetCharacterStruct(Id).Get().moveSpeed = value; } /// /// The multiplier setting to use when sprinting. Build mode (camera) and simulation mode settings are separate. /// public float SpeedSprintMultiplierSetting { get => GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().speedSprintMultiplier : playerEngine.GetCharacterStruct(Id).Get().sprintSpeedMultiplier; set => _ = GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().speedSprintMultiplier = value : playerEngine.GetCharacterStruct(Id).Get().sprintSpeedMultiplier = value; } /// /// The acceleration setting of the player. Build mode (camera) and simulation mode settings are separate. /// public float AccelerationSetting { get => GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().acceleration : playerEngine.GetCharacterStruct(Id).Get().acceleration; set => _ = GameState.IsBuildMode() ? playerEngine.GetCharacterStruct(Id).Get().acceleration = value : playerEngine.GetCharacterStruct(Id).Get().acceleration = value; } // object methods /// /// Teleport the player to the specified coordinates. /// /// The x coordinate. /// The y coordinate. /// The z coordinate. /// If set to true teleport relative to the player's current position. /// If set to true exit any seat the player is in. public void Teleport(float x, float y, float z, bool relative = true, bool exitSeat = true) { float3 location = new float3(x, y, z); if (relative) { location += Position; } playerEngine.SetLocation(Id, location, exitSeat: exitSeat); } /// /// Enter the given seat. /// /// The seat to enter. public void EnterSeat(Seat seat) { playerEngine.EnterSeat(Id, seat.Id); } /// /// Exit the seat the player is currently in. /// public void ExitSeat() { playerEngine.ExitSeat(Id); } /// /// Spawn the machine the player is holding in time running mode. /// public bool SpawnMachine() { return playerEngine.SpawnMachine(Id); } /// /// Despawn the player's machine in time running mode and place it in their hand. /// public bool DespawnMachine() { return playerEngine.DespawnMachine(Id); } /// /// Returns the block the player is currently looking at in build mode. /// /// The maximum distance from the player (default is the player's building reach) /// The block or null if not found public Block GetBlockLookedAt(float maxDistance = -1f) { var egid = playerEngine.GetThingLookedAt(Id, maxDistance); return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP && egid.groupID != WiresGUIExclusiveGroups.WireGroup ? Block.New(egid) : null; } /// /// Returns the rigid body the player is currently looking at during simulation. /// /// The maximum distance from the player (default is the player's building reach) /// The body or null if not found public SimBody GetSimBodyLookedAt(float maxDistance = -1f) { var egid = playerEngine.GetThingLookedAt(Id, maxDistance); return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP ? EcsObjectBase.GetInstance(egid, e => new SimBody(e)) : null; } /// /// Returns the wire the player is currently looking at in build mode. /// /// The maximum distance from the player (default is the player's building reach) /// The wire or null if not found public Wire GetWireLookedAt(float maxDistance = -1f) { var egid = playerEngine.GetThingLookedAt(Id, maxDistance); return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup ? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group), e => new Wire(e)) : null; } /// /// Returns the blocks that are in the player's current selection. /// /// An array of blocks or an empty array public Block[] GetSelectedBlocks() { return playerEngine.GetSelectedBlocks(Id); } public bool Equals(Player other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Id == other.Id; } public bool Equals(EGID other) { return Id == other.entityID && other.groupID == (Type == PlayerType.Local ? PlayersExclusiveGroups.LocalPlayers : PlayersExclusiveGroups.RemotePlayers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((Player) obj); } public override int GetHashCode() { return (int) Id; } public override string ToString() { return $"{nameof(Type)}: {Type}, {nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Mass)}: {Mass}"; } // internal methods internal static void Init() { Utility.GameEngineManager.AddGameEngine(playerEngine); Utility.GameEngineManager.AddGameEngine(playerEventsEngine); } } }