A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
20KB

  1. using System;
  2. using Gamecraft.Wires;
  3. using RobocraftX.Character;
  4. using RobocraftX.Character.Movement;
  5. using Unity.Mathematics;
  6. using RobocraftX.Common;
  7. using RobocraftX.Common.Players;
  8. using RobocraftX.GUI.Wires;
  9. using RobocraftX.Physics;
  10. using Svelto.ECS;
  11. using Techblox.BuildingDrone;
  12. using Techblox.Camera;
  13. using Techblox.Character;
  14. using TechbloxModdingAPI.Blocks;
  15. using TechbloxModdingAPI.Players;
  16. using TechbloxModdingAPI.Utility;
  17. using UnityEngine;
  18. namespace TechbloxModdingAPI
  19. {
  20. /// <summary>
  21. /// An in-game player character. Any Leo you see is a player.
  22. /// </summary>
  23. public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
  24. {
  25. // static functionality
  26. private static readonly PlayerEngine playerEngine = new PlayerEngine();
  27. private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
  28. private static Player localPlayer;
  29. /// <summary>
  30. /// Checks if the specified player exists.
  31. /// </summary>
  32. /// <returns>Whether the player exists.</returns>
  33. /// <param name="player">Player type.</param>
  34. public static bool Exists(PlayerType player)
  35. {
  36. switch (player)
  37. {
  38. case PlayerType.Remote:
  39. return playerEngine.GetRemotePlayer() != uint.MaxValue;
  40. case PlayerType.Local:
  41. return playerEngine.GetLocalPlayer() != uint.MaxValue;
  42. }
  43. return false;
  44. }
  45. /// <summary>
  46. /// Checks if the specified player exists.
  47. /// </summary>
  48. /// <returns>Whether the player exists.</returns>
  49. /// <param name="player">The player's unique identifier.</param>
  50. public static bool Exists(uint player)
  51. {
  52. return playerEngine.ExistsById(player);
  53. }
  54. /// <summary>
  55. /// The amount of Players in the current game.
  56. /// </summary>
  57. /// <returns>The count.</returns>
  58. public static uint Count()
  59. {
  60. return (uint) playerEngine.GetAllPlayerCount();
  61. }
  62. /// <summary>
  63. /// Returns the current player belonging to this client. It will be different after entering/leaving simulation.
  64. /// May return null if the local player doesn't exist.
  65. /// </summary>
  66. public static Player LocalPlayer
  67. {
  68. get
  69. {
  70. var playerId = playerEngine.GetLocalPlayer();
  71. if (playerId == uint.MaxValue) return null;
  72. if (localPlayer == null || localPlayer.Id != playerId)
  73. localPlayer = GetInstance(playerId);
  74. return localPlayer;
  75. }
  76. }
  77. internal static Player GetInstance(uint id)
  78. {
  79. return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
  80. e => new Player(e.entityID));
  81. }
  82. /// <summary>
  83. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
  84. /// </summary>
  85. /// <param name="id">The player's unique identifier.</param>
  86. public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup))
  87. {
  88. this.Id = id;
  89. if (!Exists(id))
  90. {
  91. throw new PlayerNotFoundException($"No player with id {id} exists");
  92. }
  93. this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote;
  94. }
  95. /// <summary>
  96. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
  97. /// </summary>
  98. /// <param name="player">The player type. Chooses the first available player matching the criteria.</param>
  99. public Player(PlayerType player) : base(ecs =>
  100. {
  101. uint id;
  102. switch (player)
  103. {
  104. case PlayerType.Local:
  105. id = playerEngine.GetLocalPlayer();
  106. break;
  107. case PlayerType.Remote:
  108. id = playerEngine.GetRemotePlayer();
  109. break;
  110. default:
  111. id = uint.MaxValue;
  112. break;
  113. }
  114. if (id == uint.MaxValue)
  115. {
  116. throw new PlayerNotFoundException($"No player of {player} type exists");
  117. }
  118. return new EGID(id, CharacterExclusiveGroups.OnFootGroup);
  119. })
  120. {
  121. this.Type = player;
  122. Id = base.Id.entityID;
  123. }
  124. // object fields & properties
  125. /// <summary>
  126. /// The player's type.
  127. /// The player type is always relative to the current client, not the game host.
  128. /// </summary>
  129. /// <value>The enumerated player type.</value>
  130. public PlayerType Type { get; }
  131. /// <summary>
  132. /// The player's unique identifier.
  133. /// </summary>
  134. /// <value>The identifier.</value>
  135. public new uint Id { get; }
  136. /// <summary>
  137. /// The player's current position.
  138. /// </summary>
  139. /// <value>The position.</value>
  140. public float3 Position
  141. {
  142. get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().position;
  143. set => playerEngine.SetLocation(Id, value, false);
  144. }
  145. /// <summary>
  146. /// The player's current rotation.
  147. /// </summary>
  148. /// <value>The rotation.</value>
  149. public float3 Rotation
  150. {
  151. get => ((Quaternion) (GameState.IsBuildMode()
  152. ? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation
  153. : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles;
  154. set => _ = GameState.IsBuildMode()
  155. ? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
  156. : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value);
  157. }
  158. /// <summary>
  159. /// The player's current velocity.
  160. /// </summary>
  161. /// <value>The velocity.</value>
  162. public float3 Velocity
  163. {
  164. get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().velocity;
  165. set => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().velocity = value;
  166. }
  167. /// <summary>
  168. /// The player's current angular velocity.
  169. /// </summary>
  170. /// <value>The angular velocity.</value>
  171. public float3 AngularVelocity
  172. {
  173. get => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().angularVelocity;
  174. set => playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().angularVelocity = value;
  175. }
  176. /// <summary>
  177. /// The player's mass.
  178. /// </summary>
  179. /// <value>The mass.</value>
  180. [Obsolete] // We cannot get it clientside or something
  181. public float Mass => 0;
  182. /// <summary>
  183. /// The player's latest network ping time.
  184. /// </summary>
  185. /// <value>The ping (s).</value>
  186. public float Ping
  187. {
  188. get
  189. {
  190. return playerEngine.GetPing() / 1000f;
  191. }
  192. }
  193. /// <summary>
  194. /// The player's initial health when entering Simulation (aka Time Running) mode.
  195. /// </summary>
  196. /// <value>The initial health.</value>
  197. [Obsolete("We can no longer get initial health, returns max health.")]
  198. public float InitialHealth
  199. {
  200. get
  201. {
  202. var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
  203. return opt ? opt.Get().maxHealth : -1f;
  204. }
  205. set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().maxHealth = value;
  206. }
  207. /// <summary>
  208. /// The player's current health in Simulation (aka Time Running) mode.
  209. /// </summary>
  210. /// <value>The current health.</value>
  211. public float CurrentHealth
  212. {
  213. get
  214. {
  215. var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
  216. return opt ? opt.Get().currentHealth : -1f;
  217. }
  218. set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().currentHealth = value;
  219. }
  220. /// <summary>
  221. /// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
  222. /// </summary>
  223. /// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
  224. [Obsolete("Players are probably always damageable")]
  225. public bool Damageable
  226. {
  227. get => true;
  228. // ReSharper disable once ValueParameterNotUsed
  229. set
  230. {
  231. }
  232. }
  233. /// <summary>
  234. /// The player's lives when initially entering Simulation (aka Time Running) mode.
  235. /// </summary>
  236. /// <value>The initial lives.</value>
  237. [Obsolete("The player has infinite lives")]
  238. public uint InitialLives
  239. {
  240. get => uint.MaxValue;
  241. // ReSharper disable once ValueParameterNotUsed
  242. set { }
  243. }
  244. /// <summary>
  245. /// The player's current lives in Simulation (aka Time Running) mode.
  246. /// </summary>
  247. /// <value>The current lives.</value>
  248. [Obsolete("The player has infinite lives")]
  249. public uint CurrentLives
  250. {
  251. get => uint.MaxValue;
  252. // ReSharper disable once ValueParameterNotUsed
  253. set { }
  254. }
  255. /*/// <summary>
  256. /// Whether the Game Over screen is displayed for the player.
  257. /// </summary>
  258. /// <value><c>true</c> if game over; otherwise, <c>false</c>.</value>
  259. public bool GameOver
  260. {
  261. get => playerEngine.GetGameOverScreen(Id);
  262. }*/
  263. /// <summary>
  264. /// Whether the player is dead.
  265. /// If <c>true</c>, hopefully it was quick.
  266. /// </summary>
  267. /// <value><c>true</c> if dead; otherwise, <c>false</c>.</value>
  268. public bool Dead
  269. {
  270. get => playerEngine.IsDead(Id);
  271. }
  272. /// <summary>
  273. /// The player's selected block ID in their hand.
  274. /// </summary>
  275. /// <value>The selected block.</value>
  276. public BlockIDs SelectedBlock
  277. {
  278. get
  279. {
  280. var optstruct = playerEngine.GetCharacterStruct<EquippedPartStruct>(Id);
  281. return optstruct ? (BlockIDs) optstruct.Get().selectedDBPartID : BlockIDs.Invalid;
  282. }
  283. }
  284. /// <summary>
  285. /// The player's selected block color in their hand.
  286. /// </summary>
  287. /// <value>The selected block's color.</value>
  288. public BlockColor SelectedColor
  289. {
  290. get
  291. {
  292. var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
  293. return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
  294. }
  295. }
  296. /// <summary>
  297. /// The player's selected block colour in their hand.
  298. /// </summary>
  299. /// <value>The selected block's colour.</value>
  300. public BlockColor SelectedColour
  301. {
  302. get
  303. {
  304. var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
  305. return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
  306. }
  307. }
  308. /// <summary>
  309. /// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage.
  310. /// </summary>
  311. public Blueprint SelectedBlueprint
  312. {
  313. get
  314. {
  315. var lbiso = playerEngine.GetPlayerStruct<LocalBlueprintInputStruct>(Id, Type);
  316. return lbiso ? new Blueprint(lbiso.Get().selectedBlueprintId) : null;
  317. }
  318. set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue);
  319. }
  320. /// <summary>
  321. /// The player's mode in time stopped mode, determining what they place.
  322. /// </summary>
  323. public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
  324. .GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
  325. public PlayerState State =>
  326. playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
  327. {
  328. var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
  329. var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
  330. var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
  331. var group when group == CharacterExclusiveGroups.DyingOnFootGroup => PlayerState.OnFoot,
  332. var group when group == CharacterExclusiveGroups.DyingInPilotSeatGroup => PlayerState.InSeat,
  333. var group when group == CharacterExclusiveGroups.DeadGroup => PlayerState.OnFoot,
  334. _ => throw new ArgumentOutOfRangeException("", "Unknown player state")
  335. };
  336. /// <summary>
  337. /// Whether the player is sprinting.
  338. /// </summary>
  339. public bool Sprinting
  340. {
  341. get => GameState.IsBuildMode()
  342. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
  343. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
  344. set => _ = GameState.IsBuildMode()
  345. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
  346. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
  347. }
  348. /// <summary>
  349. /// Movement speed setting. Build mode (camera) and simulation mode settings are separate.
  350. /// </summary>
  351. public float SpeedSetting
  352. {
  353. get => GameState.IsBuildMode()
  354. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
  355. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
  356. set => _ = GameState.IsBuildMode()
  357. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
  358. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
  359. }
  360. /// <summary>
  361. /// The multiplier setting to use when sprinting. Build mode (camera) and simulation mode settings are separate.
  362. /// </summary>
  363. public float SpeedSprintMultiplierSetting
  364. {
  365. get => GameState.IsBuildMode()
  366. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
  367. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
  368. set => _ = GameState.IsBuildMode()
  369. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
  370. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
  371. }
  372. /// <summary>
  373. /// The acceleration setting of the player. Build mode (camera) and simulation mode settings are separate.
  374. /// </summary>
  375. public float AccelerationSetting
  376. {
  377. get => GameState.IsBuildMode()
  378. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
  379. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
  380. set => _ = GameState.IsBuildMode()
  381. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
  382. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
  383. }
  384. // object methods
  385. /// <summary>
  386. /// Teleport the player to the specified coordinates.
  387. /// </summary>
  388. /// <param name="x">The x coordinate.</param>
  389. /// <param name="y">The y coordinate.</param>
  390. /// <param name="z">The z coordinate.</param>
  391. /// <param name="relative">If set to <c>true</c> teleport relative to the player's current position.</param>
  392. /// <param name="exitSeat">If set to <c>true</c> exit any seat the player is in.</param>
  393. public void Teleport(float x, float y, float z, bool relative = true, bool exitSeat = true)
  394. {
  395. float3 location = new float3(x, y, z);
  396. if (relative)
  397. {
  398. location += Position;
  399. }
  400. playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
  401. }
  402. /// <summary>
  403. /// Enter the given seat.
  404. /// </summary>
  405. /// <param name="seat">The seat to enter.</param>
  406. public void EnterSeat(Seat seat)
  407. {
  408. playerEngine.EnterSeat(Id, seat.Id);
  409. }
  410. /// <summary>
  411. /// Exit the seat the player is currently in.
  412. /// </summary>
  413. public void ExitSeat()
  414. {
  415. playerEngine.ExitSeat(Id);
  416. }
  417. /// <summary>
  418. /// Spawn the machine the player is holding in time running mode.
  419. /// </summary>
  420. public bool SpawnMachine()
  421. {
  422. return playerEngine.SpawnMachine(Id);
  423. }
  424. /// <summary>
  425. /// Despawn the player's machine in time running mode and place it in their hand.
  426. /// </summary>
  427. public bool DespawnMachine()
  428. {
  429. return playerEngine.DespawnMachine(Id);
  430. }
  431. /// <summary>
  432. /// Returns the block the player is currently looking at in build mode.
  433. /// </summary>
  434. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  435. /// <returns>The block or null if not found</returns>
  436. public Block GetBlockLookedAt(float maxDistance = -1f)
  437. {
  438. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  439. return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  440. && egid.groupID != WiresGUIExclusiveGroups.WireGroup
  441. ? Block.New(egid)
  442. : null;
  443. }
  444. /// <summary>
  445. /// Returns the rigid body the player is currently looking at during simulation.
  446. /// </summary>
  447. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  448. /// <returns>The body or null if not found</returns>
  449. public SimBody GetSimBodyLookedAt(float maxDistance = -1f)
  450. {
  451. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  452. return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  453. ? EcsObjectBase.GetInstance(egid, e => new SimBody(e))
  454. : null;
  455. }
  456. /// <summary>
  457. /// Returns the wire the player is currently looking at in build mode.
  458. /// </summary>
  459. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  460. /// <returns>The wire or null if not found</returns>
  461. public Wire GetWireLookedAt(float maxDistance = -1f)
  462. {
  463. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  464. return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
  465. ? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
  466. e => new Wire(e))
  467. : null;
  468. }
  469. /// <summary>
  470. /// Returns the blocks that are in the player's current selection.
  471. /// </summary>
  472. /// <returns>An array of blocks or an empty array</returns>
  473. public Block[] GetSelectedBlocks()
  474. {
  475. return playerEngine.GetSelectedBlocks(Id);
  476. }
  477. /// <summary>
  478. /// Returns the ghost block that shows the block to be placed in build mode.
  479. /// </summary>
  480. /// <returns>A block instance or null if not found</returns>
  481. public Block GetGhostBlock()
  482. {
  483. return playerEngine.GetGhostBlock(Id);
  484. }
  485. public bool Equals(Player other)
  486. {
  487. if (ReferenceEquals(null, other)) return false;
  488. if (ReferenceEquals(this, other)) return true;
  489. return Id == other.Id;
  490. }
  491. public bool Equals(EGID other)
  492. {
  493. return Id == other.entityID && other.groupID == (Type == PlayerType.Local
  494. ? PlayersExclusiveGroups.LocalPlayers
  495. : PlayersExclusiveGroups.RemotePlayers);
  496. }
  497. public override bool Equals(object obj)
  498. {
  499. if (ReferenceEquals(null, obj)) return false;
  500. if (ReferenceEquals(this, obj)) return true;
  501. if (obj.GetType() != this.GetType()) return false;
  502. return Equals((Player) obj);
  503. }
  504. public override int GetHashCode()
  505. {
  506. return (int) Id;
  507. }
  508. public override string ToString()
  509. {
  510. return $"{nameof(Type)}: {Type}, {nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Mass)}: {Mass}";
  511. }
  512. // internal methods
  513. internal static void Init()
  514. {
  515. Utility.GameEngineManager.AddGameEngine(playerEngine);
  516. Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
  517. }
  518. }
  519. }