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.

581 lines
20KB

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