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.

511 lines
18KB

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