Tools for building games mainly focused on changing block properties. And noclip.
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.

394 lines
19KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using DataLoader;
  6. using HarmonyLib;
  7. using TechbloxModdingAPI;
  8. using TechbloxModdingAPI.Blocks;
  9. using TechbloxModdingAPI.Commands;
  10. using TechbloxModdingAPI.Utility;
  11. using IllusionPlugin;
  12. using RobocraftX.Schedulers;
  13. using Svelto.Tasks;
  14. using Svelto.Tasks.Enumerators;
  15. using Svelto.Tasks.Lean;
  16. using TechbloxModdingAPI.App;
  17. using Unity.Mathematics;
  18. using Main = TechbloxModdingAPI.Main;
  19. namespace BuildingTools
  20. {
  21. public class BuildingTools : IEnhancedPlugin
  22. {
  23. private readonly CommandUtils _commandUtils;
  24. private readonly BlockSelections _blockSelections;
  25. //private readonly MirrorModeEngine _mirrorModeEngine = new MirrorModeEngine();
  26. public BuildingTools()
  27. {
  28. _blockSelections = new BlockSelections();
  29. _commandUtils = new CommandUtils(_blockSelections);
  30. }
  31. public override void OnApplicationStart()
  32. {
  33. Main.Init();
  34. Game.AddPersistentDebugInfo("PlayerInfo", GetPlayerInfo);
  35. Game.AddPersistentDebugInfo("BlockModInfo", GetBlockInfo);
  36. _commandUtils.RegisterBlockCommand("scaleBlocks",
  37. "Scales the selected blocks, relative to current size (current scale * new scale)." +
  38. " The block you're looking at stays where it is, everything else is moved next to it.",
  39. (scaleX, scaleY, scaleZ, blocks, refBlock) => //TODO: Either remove refBlock or add commands for changing it
  40. {
  41. if (!GameState.IsBuildMode()) return; //Scaling & positioning is weird in simulation
  42. if (_blockSelections.CheckNoBlocks(blocks)) return;
  43. float3? reference = Player.LocalPlayer.GetBlockLookedAt()?.Position;
  44. if (!reference.HasValue)
  45. {
  46. Logging.CommandLogError("Look at a block (not too far away) to be used as reference.");
  47. return;
  48. }
  49. float3 scale = new float3(scaleX, scaleY, scaleZ);
  50. foreach (var block in blocks)
  51. {
  52. block.Scale *= scale;
  53. block.Position = (float3) (reference + (block.Position - reference) * scale);
  54. }
  55. Logging.CommandLog("Blocks scaled and moved.");
  56. });
  57. _commandUtils.RegisterBlockCommand("scaleIndividually", "Scales the blocks you're looking at, but doesn't move them." +
  58. " The scale is relative, 1 means no change.",
  59. (scaleX, scaleY, scaleZ, blocks, refBlock) =>
  60. {
  61. if (!GameState.IsBuildMode()) return; //Scaling & positioning is weird in simulation
  62. float3 scale = new float3(scaleX, scaleY, scaleZ);
  63. foreach (var block in blocks)
  64. block.Scale *= scale;
  65. Logging.CommandLog("Blocks scaled individually.");
  66. });
  67. _commandUtils.RegisterBlockCommand("moveBlocks", "Moves (teleports) the selected blocks around both in time stopped and running. The latter will be reset as expected.", (x, y, z, blocks, refBlock) =>
  68. {
  69. if (GameState.IsBuildMode())
  70. foreach (var block in blocks)
  71. block.Position += new float3(x, y, z);
  72. else if (GameState.IsSimulationMode())
  73. foreach (var body in GetSimBodies(blocks))
  74. body.Position += new float3(x, y, z);
  75. Logging.CommandLog("Blocks moved.");
  76. });
  77. _commandUtils.RegisterBlockCommand("colorBlocks", "Colors the selected blocks permanently both in time stopped and running. It won't be reset when stopping time.",
  78. (color, darkness, blocks, refBlock) =>
  79. {
  80. if (!Enum.TryParse(color, true, out BlockColors clr))
  81. {
  82. Logging.CommandLogWarning("Color " + color + " not found");
  83. }
  84. foreach (var block in blocks)
  85. block.Color = new BlockColor(clr, darkness);
  86. Logging.CommandLog("Blocks colored.");
  87. });
  88. _commandUtils.RegisterBlockCommand("materialBlocks", "Sets the material of the selected blocks permanently both in time stopped and running. It won't be reset when stopping time.",
  89. (material, darkness, blocks, refBlock) =>
  90. {
  91. if (!Enum.TryParse(material, true, out BlockMaterial mat))
  92. {
  93. Logging.CommandLogWarning("Material " + material + " not found");
  94. }
  95. IEnumerator<TaskContract> SetMaterial()
  96. {
  97. foreach (var block in blocks)
  98. {
  99. block.Material = mat;
  100. yield return new WaitForSecondsEnumerator(0.2f).Continue();
  101. }
  102. }
  103. SetMaterial().RunOn(ClientLean.UIScheduler);
  104. Logging.CommandLog("Block materials set.");
  105. });
  106. CommandBuilder.Builder("selectBlocksLookedAt",
  107. "Selects blocks (1 or more) to change. Only works in time stopped mode." +
  108. " Parameter: whether one (true) or all connected (false) blocks should be selected.")
  109. .Action<bool>(single =>
  110. {
  111. if (!GameState.IsBuildMode())
  112. {
  113. Logging.CommandLogError("This command can only be used in time stopped mode.");
  114. return;
  115. }
  116. var refBlock = Player.LocalPlayer.GetBlockLookedAt();
  117. if (refBlock == null)
  118. {
  119. Logging.CommandLogError("Block not found. Make sure to be close enough to the block.");
  120. return;
  121. }
  122. _blockSelections.refBlock = refBlock;
  123. _blockSelections.blocks = single ? new[] {refBlock} : refBlock.GetConnectedCubes() ?? new Block[0];
  124. var blocks = _blockSelections.blocks;
  125. Logging.CommandLog(blocks.Length + " blocks selected.");
  126. }).Build();
  127. CommandBuilder.Builder("selectBlocksWithID", "Selects blocks with a specific object ID.")
  128. .Action<char>(id =>
  129. {
  130. _blockSelections.blocks =
  131. (_blockSelections.refBlock = ObjectID.GetByID(id).FirstOrDefault())
  132. ?.GetConnectedCubes() ?? Array.Empty<Block>();
  133. Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
  134. }).Build();
  135. CommandBuilder.Builder("selectSelectedBlocks", "Selects blocks that are box selected by the player.")
  136. .Action(() =>
  137. {
  138. _blockSelections.blocks = Player.LocalPlayer.GetSelectedBlocks();
  139. _blockSelections.refBlock = _blockSelections.blocks.Length > 0 ? _blockSelections.blocks[0] : null;
  140. Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
  141. }).Build();
  142. CommandBuilder.Builder("selectBlocksInGroup",
  143. "Selects the blocks in the block group you are looking at (the blocks currently highlighted when in blueprint mode).")
  144. .Action(() =>
  145. {
  146. var block = Player.LocalPlayer.GetBlockLookedAt();
  147. if (block is null)
  148. {
  149. Logging.CommandLogError("You need to look at a block first (and be close to it).");
  150. return;
  151. }
  152. var group = block.BlockGroup;
  153. _blockSelections.blocks = group is null ? new[] {block} : group.ToArray();
  154. _blockSelections.refBlock = block;
  155. Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
  156. }).Build();
  157. /*ConsoleCommands.RegisterWithChannel("selectSendSignal", ch => { }, ChannelType.Object,
  158. "Sends a signal for selecting a given object ID for a command block.");*/
  159. _commandUtils.RegisterBlockCommand("pushBlocks", "Adds velocity to the selected blocks. Only works in time running mode.",
  160. (x, y, z, blocks, refBlock) =>
  161. {
  162. if (!GameState.IsSimulationMode())
  163. {
  164. Logging.CommandLogError("This command can only be used in time running mode.");
  165. return;
  166. }
  167. foreach (var block in GetSimBodies(blocks))
  168. block.Velocity += new float3(x, y, z);
  169. Logging.CommandLog("Blocks pushed.");
  170. });
  171. _commandUtils.RegisterBlockCommand("pushRotateBlocks",
  172. "Adds angular velocity to the selected blocks. Only works in simulation.",
  173. (x, y, z, blocks, refBlock) =>
  174. {
  175. if (!GameState.IsSimulationMode())
  176. {
  177. Logging.CommandLogError("This command can only be used in time running mode.");
  178. return;
  179. }
  180. foreach (var block in GetSimBodies(blocks))
  181. block.AngularVelocity += new float3(x, y, z);
  182. Logging.CommandLog("Blocks pushed to rotate.");
  183. });
  184. CommandBuilder.Builder("pushPlayer", "Adds velocity to the player.")
  185. .Action<float, float, float>((x, y, z) =>
  186. {
  187. Player.LocalPlayer.Velocity += new float3(x, y, z);
  188. Logging.CommandLog("Player pushed.");
  189. }).Build();
  190. CommandBuilder.Builder("pushRotatePlayer", "Adds angular velocity to the player.")
  191. .Action<float, float, float>((x, y, z) =>
  192. {
  193. Player.LocalPlayer.AngularVelocity += new float3(x, y, z);
  194. Logging.CommandLog("Player pushed to rotate.");
  195. }).Build();
  196. CommandBuilder.Builder("addBlocksToGroup",
  197. "Adds the selected blocks to the same group (they will be highlighted together)." +
  198. " This command recreates the blocks that are moved into the group, but block data is almost certainly preserved.")
  199. .Action(() =>
  200. {
  201. if (_blockSelections.blocks.Length == 0)
  202. {
  203. Logging.CommandLogWarning("No blocks selected. Use a select command first.");
  204. return;
  205. }
  206. var group = _blockSelections.refBlock.BlockGroup;
  207. uint refID = _blockSelections.refBlock.Id.entityID;
  208. if (group is null)
  209. {
  210. var copy = _blockSelections.refBlock.Copy();
  211. group = BlockGroup.Create(copy);
  212. _blockSelections.refBlock.Remove();
  213. _blockSelections.refBlock = copy;
  214. }
  215. _blockSelections.blocks = _blockSelections.blocks.Where(block => block.Id.entityID != refID)
  216. .Select(block =>
  217. {
  218. if (block.BlockGroup == group) return block;
  219. var copy = block.Copy();
  220. group.Add(copy);
  221. block.Remove();
  222. return copy;
  223. }).ToArray();
  224. }).Build();
  225. var setLimits = new SetLimitsCommandEngine();
  226. CommandBuilder.Builder("setBuildLimits", "Set build limits").Action((Action<int, int, int>)setLimits.SetLimits).Build();
  227. GameEngineManager.AddGameEngine(setLimits);
  228. CommandBuilder.Builder("freeScaling", "This command removes scaling restrictions on the selected block. Reselect block to apply.")
  229. .Action(() =>
  230. {
  231. var blockID = Player.LocalPlayer.SelectedBlock;
  232. if (blockID == BlockIDs.Invalid)
  233. {
  234. Logging.CommandLogWarning("You don't have any blocks in your hand.");
  235. return;
  236. }
  237. FullGameFields._dataDb.GetValue<CubeListData>((int) blockID).scalingPermission =
  238. ScalingPermission.NonUniform;
  239. Logging.CommandLog("Free scaling enabled for " + blockID + " until the game is restarted. Reselect block to apply.");
  240. }).Build();
  241. CommandBuilder.Builder("setTweakLimit",
  242. "Sets the limit on the tweakable stat on selected block. Usage: setTweakLimit [stat] [value]. Sets all stats to 1000 by default.")
  243. .Action((string stat, int value) =>
  244. {
  245. var bl = Player.LocalPlayer.SelectedBlock;
  246. if (bl == BlockIDs.Invalid)
  247. {
  248. Logging.CommandLogError("Select the block in the inventory first.");
  249. return;
  250. }
  251. if (!FullGameFields._dataDb.TryGetValue<TweakableStatsData>((int)bl, out var data))
  252. {
  253. Logging.CommandLogError($"No tweakable stats found on {bl} (selected)");
  254. return;
  255. }
  256. TweakPropertyInfo[] stats;
  257. if (stat is null || stat.Length == 0)
  258. {
  259. stats = data.Stats;
  260. }
  261. else
  262. {
  263. if (!data.statsByName.TryGetValue(stat, out var statInfo))
  264. {
  265. Logging.CommandLogError($"Tweakable stat {stat} not found. Stats: {data.statsByName.Keys.Aggregate((a, b) => a + ", " + b)}");
  266. return;
  267. }
  268. stats = new[] { statInfo };
  269. }
  270. foreach (var statInfo in stats)
  271. {
  272. statInfo.max = value == 0 ? 1000 : value;
  273. statInfo.min = -1000;
  274. }
  275. Logging.CommandLog($"{(stat is null || stat.Length == 0 ? "All stats" : $"Stat {stat}")} max changed to {(value == 0 ? 1000 : value)} on {bl}");
  276. }).Build();
  277. //_mirrorModeEngine.Init();
  278. UI.Init();
  279. new Harmony("BuildTools").PatchAll(Assembly.GetExecutingAssembly());
  280. }
  281. private string GetBlockInfo()
  282. {
  283. if (GameState.IsBuildMode())
  284. return GetBlockInfoInBuildMode();
  285. if (GameState.IsSimulationMode())
  286. return GetBodyInfoInSimMode();
  287. return "Switching modes...";
  288. }
  289. private static string GetBlockInfoInBuildMode()
  290. {
  291. var block = Player.LocalPlayer.GetBlockLookedAt();
  292. if (block == null) return GetWireInfoInBuildMode();
  293. float3 pos = block.Position;
  294. float3 rot = block.Rotation;
  295. float3 scale = block.Scale;
  296. return $"Block: {block.Type} at {pos.x:F} {pos.y:F} {pos.z:F}\n" +
  297. $"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
  298. $"- Color: {block.Color.Color} darkness: {block.Color.Darkness}\n" +
  299. $"- Material: {block.Material}\n" +
  300. $"- Scale: {scale.x:F} {scale.y:F} {scale.z:F}\n" +
  301. $"- Label: {block.Label}\n" +
  302. $"- ID: {block.Id}\n" +
  303. (block.BlockGroup != null ? $"- Group: {block.BlockGroup.Id}\n" : "") +
  304. $"- Mass: {block.Mass}";
  305. }
  306. private static string GetWireInfoInBuildMode()
  307. {
  308. var wire = Player.LocalPlayer.GetWireLookedAt();
  309. if (wire == null) return "";
  310. var startPos = wire.Start.Position;
  311. var endPos = wire.End.Position;
  312. return $"Wire with {wire.Id}\n" +
  313. $"- From block {wire.Start.Type} at {startPos.x:F} {startPos.y:F} {startPos.z:F}\n" +
  314. $"- at port {wire.StartPortName}\n" +
  315. $"- To block {wire.End.Type} at {endPos.x:F} {endPos.y:F} {endPos.z:F}\n" +
  316. $"- at port {wire.EndPortName}";
  317. }
  318. private static string GetBodyInfoInSimMode()
  319. {
  320. var body = Player.LocalPlayer.GetSimBodyLookedAt();
  321. if (body == null) return GetBlockInfoInBuildMode();
  322. float3 pos = body.Position;
  323. float3 rot = body.Rotation;
  324. float3 vel = body.Velocity;
  325. float3 ave = body.AngularVelocity;
  326. float3 com = body.CenterOfMass;
  327. Cluster cluster = body.Cluster;
  328. return $"Body at {pos.x:F} {pos.y:F} {pos.z:F}\n" +
  329. $"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
  330. $"- Velocity: {vel.x:F} {vel.y:F} {vel.z:F}\n" +
  331. $"- Angular velocity: {ave.x:F} {ave.y:F} {ave.z:F}\n" +
  332. $"- {(body.Static ? "Static body" : $"Center of mass: {com.x:F} {com.y:F} {com.z:F}")}\n" +
  333. $"- Volume: {body.Volume:F}\n" +
  334. $"- Chunk health: {body.CurrentHealth:F} / {body.InitialHealth:F} - Multiplier: {body.HealthMultiplier:F}\n" +
  335. (cluster == null
  336. ? ""
  337. : $"- Cluster health: {cluster.CurrentHealth:F} / {cluster.InitialHealth:F} - Multiplier: {cluster.HealthMultiplier:F}\n" +
  338. $"- Cluster mass: {cluster.Mass}"
  339. );
  340. }
  341. private string GetPlayerInfo()
  342. {
  343. var player = Player.LocalPlayer;
  344. if (player == null) return "";
  345. float3 pos = player.Position;
  346. float3 rot = player.Rotation;
  347. float3 vel = player.Velocity;
  348. float3 ave = player.AngularVelocity;
  349. return $"Player position: {pos.x:F} {pos.y:F} {pos.z:F}\n" +
  350. $"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
  351. $"- Velocity: {vel.x:F} {vel.y:F} {vel.z:F}\n" +
  352. $"- Angular velocity: {ave.x:F} {ave.y:F} {ave.z:F}\n" +
  353. $"- Health: {player.CurrentHealth:F} / {player.InitialHealth:F}";
  354. }
  355. private IEnumerable<SimBody> GetSimBodies(Block[] blocks)
  356. => blocks.Select(block => block.GetSimBody()).Where(block => !(block is null)).Distinct();
  357. public override void OnApplicationQuit() => Main.Shutdown();
  358. public override string Name => "BuildingTools";
  359. public override string Version => "v1.1.0";
  360. }
  361. }