TechbloxModdingAPI/TechbloxModdingAPI/Player.cs
Norbi Peti 23439abde3
Add new blocks and materials, make every type public in the game, fix entity publish
- Probably should've committed more
- Added new block IDs and a material (also fixed material names)
- Added missing player states
- Added a class to make every type public in the game's code, so we don't need to worry about internal components
- We don't need to worry about anticheat so it should be fine - will need to be made into its own exe though
- Fixed delayed entity publishing and set the limits based on the consumers (just 30 almost everywhere)
2022-10-04 01:47:09 +02:00

579 lines
20 KiB
C#

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 Techblox.Character;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
using UnityEngine;
namespace TechbloxModdingAPI
{
/// <summary>
/// An in-game player character. Any Leo you see is a player.
/// </summary>
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{
// static functionality
private static readonly PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer;
/// <summary>
/// Checks if the specified player exists.
/// </summary>
/// <returns>Whether the player exists.</returns>
/// <param name="player">Player type.</param>
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;
}
/// <summary>
/// Checks if the specified player exists.
/// </summary>
/// <returns>Whether the player exists.</returns>
/// <param name="player">The player's unique identifier.</param>
public static bool Exists(uint player)
{
return playerEngine.ExistsById(player);
}
/// <summary>
/// The amount of Players in the current game.
/// </summary>
/// <returns>The count.</returns>
public static uint Count()
{
return (uint) playerEngine.GetAllPlayerCount();
}
/// <summary>
/// Returns the current player belonging to this client. It will be different after entering/leaving simulation.
/// May return null if the local player doesn't exist.
/// </summary>
public static Player LocalPlayer
{
get
{
var playerId = playerEngine.GetLocalPlayer();
if (playerId == uint.MaxValue) return null;
if (localPlayer == null || localPlayer.Id != playerId)
localPlayer = GetInstance(playerId);
return localPlayer;
}
}
internal static Player GetInstance(uint id)
{
return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="id">The player's unique identifier.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param>
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
/// <summary>
/// The player's type.
/// The player type is always relative to the current client, not the game host.
/// </summary>
/// <value>The enumerated player type.</value>
public PlayerType Type { get; }
/// <summary>
/// The player's unique identifier.
/// </summary>
/// <value>The identifier.</value>
public new uint Id { get; }
/// <summary>
/// The player's current position.
/// </summary>
/// <value>The position.</value>
public float3 Position
{
get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().position;
set => playerEngine.SetLocation(Id, value, false);
}
/// <summary>
/// The player's current rotation.
/// </summary>
/// <value>The rotation.</value>
public float3 Rotation
{
get => ((Quaternion) (GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value);
}
/// <summary>
/// The player's current velocity.
/// </summary>
/// <value>The velocity.</value>
public float3 Velocity
{
get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().velocity;
set => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().velocity = value;
}
/// <summary>
/// The player's current angular velocity.
/// </summary>
/// <value>The angular velocity.</value>
public float3 AngularVelocity
{
get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().angularVelocity;
set => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().angularVelocity = value;
}
/// <summary>
/// The player's mass.
/// </summary>
/// <value>The mass.</value>
[Obsolete] // We cannot get it clientside or something
public float Mass => 0;
/// <summary>
/// The player's latest network ping time.
/// </summary>
/// <value>The ping (s).</value>
public float Ping
{
get
{
return playerEngine.GetPing() / 1000f;
}
}
/// <summary>
/// The player's initial health when entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial health.</value>
[Obsolete("We can no longer get initial health, returns max health.")]
public float InitialHealth
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
return opt ? opt.Get().maxHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().maxHealth = value;
}
/// <summary>
/// The player's current health in Simulation (aka Time Running) mode.
/// </summary>
/// <value>The current health.</value>
public float CurrentHealth
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
return opt ? opt.Get().currentHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().currentHealth = value;
}
/// <summary>
/// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
/// </summary>
/// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
[Obsolete("Players are probably always damageable")]
public bool Damageable
{
get => true;
// ReSharper disable once ValueParameterNotUsed
set
{
}
}
/// <summary>
/// The player's lives when initially entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial lives.</value>
[Obsolete("The player has infinite lives")]
public uint InitialLives
{
get => uint.MaxValue;
// ReSharper disable once ValueParameterNotUsed
set { }
}
/// <summary>
/// The player's current lives in Simulation (aka Time Running) mode.
/// </summary>
/// <value>The current lives.</value>
[Obsolete("The player has infinite lives")]
public uint CurrentLives
{
get => uint.MaxValue;
// ReSharper disable once ValueParameterNotUsed
set { }
}
/*/// <summary>
/// Whether the Game Over screen is displayed for the player.
/// </summary>
/// <value><c>true</c> if game over; otherwise, <c>false</c>.</value>
public bool GameOver
{
get => playerEngine.GetGameOverScreen(Id);
}*/
/// <summary>
/// Whether the player is dead.
/// If <c>true</c>, hopefully it was quick.
/// </summary>
/// <value><c>true</c> if dead; otherwise, <c>false</c>.</value>
public bool Dead
{
get => playerEngine.IsDead(Id);
}
/// <summary>
/// The player's selected block ID in their hand.
/// </summary>
/// <value>The selected block.</value>
public BlockIDs SelectedBlock
{
get
{
var optstruct = playerEngine.GetCharacterStruct<EquippedPartStruct>(Id);
return optstruct ? (BlockIDs) optstruct.Get().selectedDBPartID : BlockIDs.Invalid;
}
}
/// <summary>
/// The player's selected block color in their hand.
/// </summary>
/// <value>The selected block's color.</value>
public BlockColor SelectedColor
{
get
{
var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
}
}
/// <summary>
/// The player's selected block colour in their hand.
/// </summary>
/// <value>The selected block's colour.</value>
public BlockColor SelectedColour
{
get
{
var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
}
}
/// <summary>
/// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage.
/// </summary>
public Blueprint SelectedBlueprint
{
get
{
var lbiso = playerEngine.GetPlayerStruct<LocalBlueprintInputStruct>(Id, Type);
return lbiso ? new Blueprint(lbiso.Get().selectedBlueprintId) : null;
}
set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue);
}
/// <summary>
/// The player's mode in time stopped mode, determining what they place.
/// </summary>
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
.GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
public PlayerState State =>
playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
{
var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DyingOnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.DyingInPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DeadGroup => PlayerState.OnFoot,
_ => throw new ArgumentOutOfRangeException("", "Unknown player state")
};
/// <summary>
/// Whether the player is sprinting.
/// </summary>
public bool Sprinting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
}
/// <summary>
/// Movement speed setting. Build mode (camera) and simulation mode settings are separate.
/// </summary>
public float SpeedSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
}
/// <summary>
/// The multiplier setting to use when sprinting. Build mode (camera) and simulation mode settings are separate.
/// </summary>
public float SpeedSprintMultiplierSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
}
/// <summary>
/// The acceleration setting of the player. Build mode (camera) and simulation mode settings are separate.
/// </summary>
public float AccelerationSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
}
// object methods
/// <summary>
/// Teleport the player to the specified coordinates.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="z">The z coordinate.</param>
/// <param name="relative">If set to <c>true</c> teleport relative to the player's current position.</param>
/// <param name="exitSeat">If set to <c>true</c> exit any seat the player is in.</param>
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);
}
/// <summary>
/// Enter the given seat.
/// </summary>
/// <param name="seat">The seat to enter.</param>
public void EnterSeat(Seat seat)
{
playerEngine.EnterSeat(Id, seat.Id);
}
/// <summary>
/// Exit the seat the player is currently in.
/// </summary>
public void ExitSeat()
{
playerEngine.ExitSeat(Id);
}
/// <summary>
/// Spawn the machine the player is holding in time running mode.
/// </summary>
public bool SpawnMachine()
{
return playerEngine.SpawnMachine(Id);
}
/// <summary>
/// Despawn the player's machine in time running mode and place it in their hand.
/// </summary>
public bool DespawnMachine()
{
return playerEngine.DespawnMachine(Id);
}
/// <summary>
/// Returns the block the player is currently looking at in build mode.
/// </summary>
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
/// <returns>The block or null if not found</returns>
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;
}
/// <summary>
/// Returns the rigid body the player is currently looking at during simulation.
/// </summary>
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
/// <returns>The body or null if not found</returns>
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;
}
/// <summary>
/// Returns the wire the player is currently looking at in build mode.
/// </summary>
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
/// <returns>The wire or null if not found</returns>
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;
}
/// <summary>
/// Returns the blocks that are in the player's current selection.
/// </summary>
/// <returns>An array of blocks or an empty array</returns>
public Block[] GetSelectedBlocks()
{
return playerEngine.GetSelectedBlocks(Id);
}
/// <summary>
/// Returns the ghost block that shows the block to be placed in build mode.
/// </summary>
/// <returns>A block instance or null if not found</returns>
public Block GetGhostBlock()
{
return playerEngine.GetGhostBlock(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);
}
}
}