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.

576 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. _ => throw new ArgumentOutOfRangeException("", "Unknown player state")
  332. };
  333. /// <summary>
  334. /// Whether the player is sprinting.
  335. /// </summary>
  336. public bool Sprinting
  337. {
  338. get => GameState.IsBuildMode()
  339. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
  340. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
  341. set => _ = GameState.IsBuildMode()
  342. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
  343. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
  344. }
  345. /// <summary>
  346. /// Movement speed setting. Build mode (camera) and simulation mode settings are separate.
  347. /// </summary>
  348. public float SpeedSetting
  349. {
  350. get => GameState.IsBuildMode()
  351. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
  352. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
  353. set => _ = GameState.IsBuildMode()
  354. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
  355. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
  356. }
  357. /// <summary>
  358. /// The multiplier setting to use when sprinting. Build mode (camera) and simulation mode settings are separate.
  359. /// </summary>
  360. public float SpeedSprintMultiplierSetting
  361. {
  362. get => GameState.IsBuildMode()
  363. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
  364. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
  365. set => _ = GameState.IsBuildMode()
  366. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
  367. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
  368. }
  369. /// <summary>
  370. /// The acceleration setting of the player. Build mode (camera) and simulation mode settings are separate.
  371. /// </summary>
  372. public float AccelerationSetting
  373. {
  374. get => GameState.IsBuildMode()
  375. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
  376. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
  377. set => _ = GameState.IsBuildMode()
  378. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
  379. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
  380. }
  381. // object methods
  382. /// <summary>
  383. /// Teleport the player to the specified coordinates.
  384. /// </summary>
  385. /// <param name="x">The x coordinate.</param>
  386. /// <param name="y">The y coordinate.</param>
  387. /// <param name="z">The z coordinate.</param>
  388. /// <param name="relative">If set to <c>true</c> teleport relative to the player's current position.</param>
  389. /// <param name="exitSeat">If set to <c>true</c> exit any seat the player is in.</param>
  390. public void Teleport(float x, float y, float z, bool relative = true, bool exitSeat = true)
  391. {
  392. float3 location = new float3(x, y, z);
  393. if (relative)
  394. {
  395. location += Position;
  396. }
  397. playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
  398. }
  399. /// <summary>
  400. /// Enter the given seat.
  401. /// </summary>
  402. /// <param name="seat">The seat to enter.</param>
  403. public void EnterSeat(Seat seat)
  404. {
  405. playerEngine.EnterSeat(Id, seat.Id);
  406. }
  407. /// <summary>
  408. /// Exit the seat the player is currently in.
  409. /// </summary>
  410. public void ExitSeat()
  411. {
  412. playerEngine.ExitSeat(Id);
  413. }
  414. /// <summary>
  415. /// Spawn the machine the player is holding in time running mode.
  416. /// </summary>
  417. public bool SpawnMachine()
  418. {
  419. return playerEngine.SpawnMachine(Id);
  420. }
  421. /// <summary>
  422. /// Despawn the player's machine in time running mode and place it in their hand.
  423. /// </summary>
  424. public bool DespawnMachine()
  425. {
  426. return playerEngine.DespawnMachine(Id);
  427. }
  428. /// <summary>
  429. /// Returns the block the player is currently looking at in build mode.
  430. /// </summary>
  431. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  432. /// <returns>The block or null if not found</returns>
  433. public Block GetBlockLookedAt(float maxDistance = -1f)
  434. {
  435. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  436. return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  437. && egid.groupID != WiresGUIExclusiveGroups.WireGroup
  438. ? Block.New(egid)
  439. : null;
  440. }
  441. /// <summary>
  442. /// Returns the rigid body the player is currently looking at during simulation.
  443. /// </summary>
  444. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  445. /// <returns>The body or null if not found</returns>
  446. public SimBody GetSimBodyLookedAt(float maxDistance = -1f)
  447. {
  448. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  449. return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  450. ? EcsObjectBase.GetInstance(egid, e => new SimBody(e))
  451. : null;
  452. }
  453. /// <summary>
  454. /// Returns the wire the player is currently looking at in build mode.
  455. /// </summary>
  456. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  457. /// <returns>The wire or null if not found</returns>
  458. public Wire GetWireLookedAt(float maxDistance = -1f)
  459. {
  460. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  461. return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
  462. ? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
  463. e => new Wire(e))
  464. : null;
  465. }
  466. /// <summary>
  467. /// Returns the blocks that are in the player's current selection.
  468. /// </summary>
  469. /// <returns>An array of blocks or an empty array</returns>
  470. public Block[] GetSelectedBlocks()
  471. {
  472. return playerEngine.GetSelectedBlocks(Id);
  473. }
  474. /// <summary>
  475. /// Returns the ghost block that shows the block to be placed in build mode.
  476. /// </summary>
  477. /// <returns>A block instance or null if not found</returns>
  478. public Block GetGhostBlock()
  479. {
  480. return playerEngine.GetGhostBlock(Id);
  481. }
  482. public bool Equals(Player other)
  483. {
  484. if (ReferenceEquals(null, other)) return false;
  485. if (ReferenceEquals(this, other)) return true;
  486. return Id == other.Id;
  487. }
  488. public bool Equals(EGID other)
  489. {
  490. return Id == other.entityID && other.groupID == (Type == PlayerType.Local
  491. ? PlayersExclusiveGroups.LocalPlayers
  492. : PlayersExclusiveGroups.RemotePlayers);
  493. }
  494. public override bool Equals(object obj)
  495. {
  496. if (ReferenceEquals(null, obj)) return false;
  497. if (ReferenceEquals(this, obj)) return true;
  498. if (obj.GetType() != this.GetType()) return false;
  499. return Equals((Player) obj);
  500. }
  501. public override int GetHashCode()
  502. {
  503. return (int) Id;
  504. }
  505. public override string ToString()
  506. {
  507. return $"{nameof(Type)}: {Type}, {nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Mass)}: {Mass}";
  508. }
  509. // internal methods
  510. internal static void Init()
  511. {
  512. Utility.GameEngineManager.AddGameEngine(playerEngine);
  513. Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
  514. }
  515. }
  516. }