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.

532 lines
19KB

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