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.

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