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.

584 lines
21KB

  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. /// 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. public float InitialHealth
  198. {
  199. get
  200. {
  201. var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
  202. return opt ? opt.Get().initialHealth : -1f;
  203. }
  204. set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().initialHealth = value;
  205. }
  206. /// <summary>
  207. /// The player's current health in Simulation (aka Time Running) mode.
  208. /// </summary>
  209. /// <value>The current health.</value>
  210. public float CurrentHealth
  211. {
  212. get
  213. {
  214. var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
  215. return opt ? opt.Get().currentHealth : -1f;
  216. }
  217. set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().currentHealth = value;
  218. }
  219. /// <summary>
  220. /// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
  221. /// </summary>
  222. /// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
  223. public bool Damageable
  224. {
  225. get
  226. {
  227. var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
  228. return opt.Get().canTakeDamageStat;
  229. }
  230. set
  231. {
  232. ref var healthStruct = ref playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get();
  233. healthStruct.canTakeDamage = value;
  234. healthStruct.canTakeDamageStat = value;
  235. }
  236. }
  237. /// <summary>
  238. /// The player's lives when initially entering Simulation (aka Time Running) mode.
  239. /// </summary>
  240. /// <value>The initial lives.</value>
  241. public uint InitialLives
  242. {
  243. get
  244. {
  245. var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
  246. return opt ? opt.Get().initialLives : uint.MaxValue;
  247. }
  248. set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().initialLives = value;
  249. }
  250. /// <summary>
  251. /// The player's current lives in Simulation (aka Time Running) mode.
  252. /// </summary>
  253. /// <value>The current lives.</value>
  254. public uint CurrentLives
  255. {
  256. get
  257. {
  258. var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
  259. return opt ? opt.Get().currentLives : uint.MaxValue;
  260. }
  261. set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().currentLives = value;
  262. }
  263. /*/// <summary>
  264. /// Whether the Game Over screen is displayed for the player.
  265. /// </summary>
  266. /// <value><c>true</c> if game over; otherwise, <c>false</c>.</value>
  267. public bool GameOver
  268. {
  269. get => playerEngine.GetGameOverScreen(Id);
  270. }*/
  271. /// <summary>
  272. /// Whether the player is dead.
  273. /// If <c>true</c>, hopefully it was quick.
  274. /// </summary>
  275. /// <value><c>true</c> if dead; otherwise, <c>false</c>.</value>
  276. public bool Dead
  277. {
  278. get => playerEngine.IsDead(Id);
  279. }
  280. /// <summary>
  281. /// The player's selected block ID in their hand.
  282. /// </summary>
  283. /// <value>The selected block.</value>
  284. public BlockIDs SelectedBlock
  285. {
  286. get
  287. {
  288. var optstruct = playerEngine.GetCharacterStruct<EquippedPartStruct>(Id);
  289. return optstruct ? (BlockIDs) optstruct.Get().selectedDBPartID : BlockIDs.Invalid;
  290. }
  291. }
  292. /// <summary>
  293. /// The player's selected block color in their hand.
  294. /// </summary>
  295. /// <value>The selected block's color.</value>
  296. public BlockColor SelectedColor
  297. {
  298. get
  299. {
  300. var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
  301. return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
  302. }
  303. }
  304. /// <summary>
  305. /// The player's selected block colour in their hand.
  306. /// </summary>
  307. /// <value>The selected block's colour.</value>
  308. public BlockColor SelectedColour
  309. {
  310. get
  311. {
  312. var optstruct = playerEngine.GetCharacterStruct<EquippedColourStruct>(Id);
  313. return optstruct ? new BlockColor(optstruct.Get().indexInPalette) : BlockColors.Default;
  314. }
  315. }
  316. /// <summary>
  317. /// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage.
  318. /// </summary>
  319. public Blueprint SelectedBlueprint
  320. {
  321. get
  322. {
  323. var lbiso = playerEngine.GetPlayerStruct<LocalBlueprintInputStruct>(Id, Type);
  324. return lbiso ? new Blueprint(lbiso.Get().selectedBlueprintId) : null;
  325. }
  326. set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue);
  327. }
  328. /// <summary>
  329. /// The player's mode in time stopped mode, determining what they place.
  330. /// </summary>
  331. public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
  332. .GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
  333. public PlayerState State =>
  334. playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
  335. {
  336. var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
  337. var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
  338. var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
  339. _ => throw new ArgumentOutOfRangeException("", "Unknown player state")
  340. };
  341. /// <summary>
  342. /// Whether the player is sprinting.
  343. /// </summary>
  344. public bool Sprinting
  345. {
  346. get => GameState.IsBuildMode()
  347. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
  348. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
  349. set => _ = GameState.IsBuildMode()
  350. ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
  351. : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
  352. }
  353. /// <summary>
  354. /// Movement speed setting. Build mode (camera) and simulation mode settings are separate.
  355. /// </summary>
  356. public float SpeedSetting
  357. {
  358. get => GameState.IsBuildMode()
  359. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
  360. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
  361. set => _ = GameState.IsBuildMode()
  362. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
  363. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
  364. }
  365. /// <summary>
  366. /// The multiplier setting to use when sprinting. Build mode (camera) and simulation mode settings are separate.
  367. /// </summary>
  368. public float SpeedSprintMultiplierSetting
  369. {
  370. get => GameState.IsBuildMode()
  371. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
  372. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
  373. set => _ = GameState.IsBuildMode()
  374. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
  375. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
  376. }
  377. /// <summary>
  378. /// The acceleration setting of the player. Build mode (camera) and simulation mode settings are separate.
  379. /// </summary>
  380. public float AccelerationSetting
  381. {
  382. get => GameState.IsBuildMode()
  383. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
  384. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
  385. set => _ = GameState.IsBuildMode()
  386. ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
  387. : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
  388. }
  389. // object methods
  390. /// <summary>
  391. /// Teleport the player to the specified coordinates.
  392. /// </summary>
  393. /// <param name="x">The x coordinate.</param>
  394. /// <param name="y">The y coordinate.</param>
  395. /// <param name="z">The z coordinate.</param>
  396. /// <param name="relative">If set to <c>true</c> teleport relative to the player's current position.</param>
  397. /// <param name="exitSeat">If set to <c>true</c> exit any seat the player is in.</param>
  398. public void Teleport(float x, float y, float z, bool relative = true, bool exitSeat = true)
  399. {
  400. float3 location = new float3(x, y, z);
  401. if (relative)
  402. {
  403. location += Position;
  404. }
  405. playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
  406. }
  407. /// <summary>
  408. /// Enter the given seat.
  409. /// </summary>
  410. /// <param name="seat">The seat to enter.</param>
  411. public void EnterSeat(Seat seat)
  412. {
  413. playerEngine.EnterSeat(Id, seat.Id);
  414. }
  415. /// <summary>
  416. /// Exit the seat the player is currently in.
  417. /// </summary>
  418. public void ExitSeat()
  419. {
  420. playerEngine.ExitSeat(Id);
  421. }
  422. /// <summary>
  423. /// Spawn the machine the player is holding in time running mode.
  424. /// </summary>
  425. public bool SpawnMachine()
  426. {
  427. return playerEngine.SpawnMachine(Id);
  428. }
  429. /// <summary>
  430. /// Despawn the player's machine in time running mode and place it in their hand.
  431. /// </summary>
  432. public bool DespawnMachine()
  433. {
  434. return playerEngine.DespawnMachine(Id);
  435. }
  436. /// <summary>
  437. /// Returns the block the player is currently looking at in build mode.
  438. /// </summary>
  439. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  440. /// <returns>The block or null if not found</returns>
  441. public Block GetBlockLookedAt(float maxDistance = -1f)
  442. {
  443. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  444. return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  445. && egid.groupID != WiresGUIExclusiveGroups.WireGroup
  446. ? Block.New(egid)
  447. : null;
  448. }
  449. /// <summary>
  450. /// Returns the rigid body the player is currently looking at during simulation.
  451. /// </summary>
  452. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  453. /// <returns>The body or null if not found</returns>
  454. public SimBody GetSimBodyLookedAt(float maxDistance = -1f)
  455. {
  456. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  457. return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
  458. ? EcsObjectBase.GetInstance(egid, e => new SimBody(e))
  459. : null;
  460. }
  461. /// <summary>
  462. /// Returns the wire the player is currently looking at in build mode.
  463. /// </summary>
  464. /// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
  465. /// <returns>The wire or null if not found</returns>
  466. public Wire GetWireLookedAt(float maxDistance = -1f)
  467. {
  468. var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
  469. return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
  470. ? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
  471. e => new Wire(e))
  472. : null;
  473. }
  474. /// <summary>
  475. /// Returns the blocks that are in the player's current selection.
  476. /// </summary>
  477. /// <returns>An array of blocks or an empty array</returns>
  478. public Block[] GetSelectedBlocks()
  479. {
  480. return playerEngine.GetSelectedBlocks(Id);
  481. }
  482. /// <summary>
  483. /// Returns the ghost block that shows the block to be placed in build mode.
  484. /// </summary>
  485. /// <returns>A block instance or null if not found</returns>
  486. public Block GetGhostBlock()
  487. {
  488. return playerEngine.GetGhostBlock(Id);
  489. }
  490. public bool Equals(Player other)
  491. {
  492. if (ReferenceEquals(null, other)) return false;
  493. if (ReferenceEquals(this, other)) return true;
  494. return Id == other.Id;
  495. }
  496. public bool Equals(EGID other)
  497. {
  498. return Id == other.entityID && other.groupID == (Type == PlayerType.Local
  499. ? PlayersExclusiveGroups.LocalPlayers
  500. : PlayersExclusiveGroups.RemotePlayers);
  501. }
  502. public override bool Equals(object obj)
  503. {
  504. if (ReferenceEquals(null, obj)) return false;
  505. if (ReferenceEquals(this, obj)) return true;
  506. if (obj.GetType() != this.GetType()) return false;
  507. return Equals((Player) obj);
  508. }
  509. public override int GetHashCode()
  510. {
  511. return (int) Id;
  512. }
  513. public override string ToString()
  514. {
  515. return $"{nameof(Type)}: {Type}, {nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Mass)}: {Mass}";
  516. }
  517. // internal methods
  518. internal static void Init()
  519. {
  520. Utility.GameEngineManager.AddGameEngine(playerEngine);
  521. Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
  522. }
  523. }
  524. }