394 lines
No EOL
19 KiB
C#
394 lines
No EOL
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using DataLoader;
|
|
using HarmonyLib;
|
|
using TechbloxModdingAPI;
|
|
using TechbloxModdingAPI.Blocks;
|
|
using TechbloxModdingAPI.Commands;
|
|
using TechbloxModdingAPI.Utility;
|
|
using IllusionPlugin;
|
|
using RobocraftX.Schedulers;
|
|
using Svelto.Tasks;
|
|
using Svelto.Tasks.Enumerators;
|
|
using Svelto.Tasks.Lean;
|
|
using TechbloxModdingAPI.App;
|
|
using Unity.Mathematics;
|
|
using Main = TechbloxModdingAPI.Main;
|
|
|
|
namespace BuildingTools
|
|
{
|
|
public class BuildingTools : IEnhancedPlugin
|
|
{
|
|
private readonly CommandUtils _commandUtils;
|
|
private readonly BlockSelections _blockSelections;
|
|
|
|
//private readonly MirrorModeEngine _mirrorModeEngine = new MirrorModeEngine();
|
|
|
|
public BuildingTools()
|
|
{
|
|
_blockSelections = new BlockSelections();
|
|
_commandUtils = new CommandUtils(_blockSelections);
|
|
}
|
|
|
|
public override void OnApplicationStart()
|
|
{
|
|
Main.Init();
|
|
Game.AddPersistentDebugInfo("PlayerInfo", GetPlayerInfo);
|
|
Game.AddPersistentDebugInfo("BlockModInfo", GetBlockInfo);
|
|
_commandUtils.RegisterBlockCommand("scaleBlocks",
|
|
"Scales the selected blocks, relative to current size (current scale * new scale)." +
|
|
" The block you're looking at stays where it is, everything else is moved next to it.",
|
|
(scaleX, scaleY, scaleZ, blocks, refBlock) => //TODO: Either remove refBlock or add commands for changing it
|
|
{
|
|
if (!GameState.IsBuildMode()) return; //Scaling & positioning is weird in simulation
|
|
if (_blockSelections.CheckNoBlocks(blocks)) return;
|
|
float3? reference = Player.LocalPlayer.GetBlockLookedAt()?.Position;
|
|
if (!reference.HasValue)
|
|
{
|
|
Logging.CommandLogError("Look at a block (not too far away) to be used as reference.");
|
|
return;
|
|
}
|
|
float3 scale = new float3(scaleX, scaleY, scaleZ);
|
|
foreach (var block in blocks)
|
|
{
|
|
block.Scale *= scale;
|
|
block.Position = (float3) (reference + (block.Position - reference) * scale);
|
|
}
|
|
|
|
Logging.CommandLog("Blocks scaled and moved.");
|
|
});
|
|
_commandUtils.RegisterBlockCommand("scaleIndividually", "Scales the blocks you're looking at, but doesn't move them." +
|
|
" The scale is relative, 1 means no change.",
|
|
(scaleX, scaleY, scaleZ, blocks, refBlock) =>
|
|
{
|
|
if (!GameState.IsBuildMode()) return; //Scaling & positioning is weird in simulation
|
|
float3 scale = new float3(scaleX, scaleY, scaleZ);
|
|
foreach (var block in blocks)
|
|
block.Scale *= scale;
|
|
Logging.CommandLog("Blocks scaled individually.");
|
|
});
|
|
_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) =>
|
|
{
|
|
if (GameState.IsBuildMode())
|
|
foreach (var block in blocks)
|
|
block.Position += new float3(x, y, z);
|
|
else if (GameState.IsSimulationMode())
|
|
foreach (var body in GetSimBodies(blocks))
|
|
body.Position += new float3(x, y, z);
|
|
Logging.CommandLog("Blocks moved.");
|
|
});
|
|
_commandUtils.RegisterBlockCommand("colorBlocks", "Colors the selected blocks permanently both in time stopped and running. It won't be reset when stopping time.",
|
|
(color, darkness, blocks, refBlock) =>
|
|
{
|
|
|
|
if (!Enum.TryParse(color, true, out BlockColors clr))
|
|
{
|
|
Logging.CommandLogWarning("Color " + color + " not found");
|
|
}
|
|
|
|
foreach (var block in blocks)
|
|
block.Color = new BlockColor(clr, darkness);
|
|
Logging.CommandLog("Blocks colored.");
|
|
});
|
|
_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.",
|
|
(material, darkness, blocks, refBlock) =>
|
|
{
|
|
if (!Enum.TryParse(material, true, out BlockMaterial mat))
|
|
{
|
|
Logging.CommandLogWarning("Material " + material + " not found");
|
|
}
|
|
|
|
IEnumerator<TaskContract> SetMaterial()
|
|
{
|
|
foreach (var block in blocks)
|
|
{
|
|
block.Material = mat;
|
|
yield return new WaitForSecondsEnumerator(0.2f).Continue();
|
|
}
|
|
}
|
|
|
|
SetMaterial().RunOn(ClientLean.UIScheduler);
|
|
Logging.CommandLog("Block materials set.");
|
|
});
|
|
|
|
CommandBuilder.Builder("selectBlocksLookedAt",
|
|
"Selects blocks (1 or more) to change. Only works in time stopped mode." +
|
|
" Parameter: whether one (true) or all connected (false) blocks should be selected.")
|
|
.Action<bool>(single =>
|
|
{
|
|
if (!GameState.IsBuildMode())
|
|
{
|
|
Logging.CommandLogError("This command can only be used in time stopped mode.");
|
|
return;
|
|
}
|
|
var refBlock = Player.LocalPlayer.GetBlockLookedAt();
|
|
if (refBlock == null)
|
|
{
|
|
Logging.CommandLogError("Block not found. Make sure to be close enough to the block.");
|
|
return;
|
|
}
|
|
_blockSelections.refBlock = refBlock;
|
|
_blockSelections.blocks = single ? new[] {refBlock} : refBlock.GetConnectedCubes() ?? new Block[0];
|
|
var blocks = _blockSelections.blocks;
|
|
Logging.CommandLog(blocks.Length + " blocks selected.");
|
|
}).Build();
|
|
CommandBuilder.Builder("selectBlocksWithID", "Selects blocks with a specific object ID.")
|
|
.Action<char>(id =>
|
|
{
|
|
_blockSelections.blocks =
|
|
(_blockSelections.refBlock = ObjectID.GetByID(id).FirstOrDefault())
|
|
?.GetConnectedCubes() ?? Array.Empty<Block>();
|
|
Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
|
|
}).Build();
|
|
CommandBuilder.Builder("selectSelectedBlocks", "Selects blocks that are box selected by the player.")
|
|
.Action(() =>
|
|
{
|
|
_blockSelections.blocks = Player.LocalPlayer.GetSelectedBlocks();
|
|
_blockSelections.refBlock = _blockSelections.blocks.Length > 0 ? _blockSelections.blocks[0] : null;
|
|
Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
|
|
}).Build();
|
|
CommandBuilder.Builder("selectBlocksInGroup",
|
|
"Selects the blocks in the block group you are looking at (the blocks currently highlighted when in blueprint mode).")
|
|
.Action(() =>
|
|
{
|
|
var block = Player.LocalPlayer.GetBlockLookedAt();
|
|
if (block is null)
|
|
{
|
|
Logging.CommandLogError("You need to look at a block first (and be close to it).");
|
|
return;
|
|
}
|
|
|
|
var group = block.BlockGroup;
|
|
_blockSelections.blocks = group is null ? new[] {block} : group.ToArray();
|
|
_blockSelections.refBlock = block;
|
|
Logging.CommandLog(_blockSelections.blocks.Length + " blocks selected.");
|
|
}).Build();
|
|
|
|
/*ConsoleCommands.RegisterWithChannel("selectSendSignal", ch => { }, ChannelType.Object,
|
|
"Sends a signal for selecting a given object ID for a command block.");*/
|
|
|
|
_commandUtils.RegisterBlockCommand("pushBlocks", "Adds velocity to the selected blocks. Only works in time running mode.",
|
|
(x, y, z, blocks, refBlock) =>
|
|
{
|
|
if (!GameState.IsSimulationMode())
|
|
{
|
|
Logging.CommandLogError("This command can only be used in time running mode.");
|
|
return;
|
|
}
|
|
foreach (var block in GetSimBodies(blocks))
|
|
block.Velocity += new float3(x, y, z);
|
|
Logging.CommandLog("Blocks pushed.");
|
|
});
|
|
_commandUtils.RegisterBlockCommand("pushRotateBlocks",
|
|
"Adds angular velocity to the selected blocks. Only works in simulation.",
|
|
(x, y, z, blocks, refBlock) =>
|
|
{
|
|
if (!GameState.IsSimulationMode())
|
|
{
|
|
Logging.CommandLogError("This command can only be used in time running mode.");
|
|
return;
|
|
}
|
|
foreach (var block in GetSimBodies(blocks))
|
|
block.AngularVelocity += new float3(x, y, z);
|
|
Logging.CommandLog("Blocks pushed to rotate.");
|
|
});
|
|
CommandBuilder.Builder("pushPlayer", "Adds velocity to the player.")
|
|
.Action<float, float, float>((x, y, z) =>
|
|
{
|
|
Player.LocalPlayer.Velocity += new float3(x, y, z);
|
|
Logging.CommandLog("Player pushed.");
|
|
}).Build();
|
|
CommandBuilder.Builder("pushRotatePlayer", "Adds angular velocity to the player.")
|
|
.Action<float, float, float>((x, y, z) =>
|
|
{
|
|
Player.LocalPlayer.AngularVelocity += new float3(x, y, z);
|
|
Logging.CommandLog("Player pushed to rotate.");
|
|
}).Build();
|
|
CommandBuilder.Builder("addBlocksToGroup",
|
|
"Adds the selected blocks to the same group (they will be highlighted together)." +
|
|
" This command recreates the blocks that are moved into the group, but block data is almost certainly preserved.")
|
|
.Action(() =>
|
|
{
|
|
if (_blockSelections.blocks.Length == 0)
|
|
{
|
|
Logging.CommandLogWarning("No blocks selected. Use a select command first.");
|
|
return;
|
|
}
|
|
|
|
var group = _blockSelections.refBlock.BlockGroup;
|
|
uint refID = _blockSelections.refBlock.Id.entityID;
|
|
if (group is null)
|
|
{
|
|
var copy = _blockSelections.refBlock.Copy();
|
|
group = BlockGroup.Create(copy);
|
|
_blockSelections.refBlock.Remove();
|
|
_blockSelections.refBlock = copy;
|
|
}
|
|
|
|
_blockSelections.blocks = _blockSelections.blocks.Where(block => block.Id.entityID != refID)
|
|
.Select(block =>
|
|
{
|
|
if (block.BlockGroup == group) return block;
|
|
var copy = block.Copy();
|
|
group.Add(copy);
|
|
block.Remove();
|
|
return copy;
|
|
}).ToArray();
|
|
}).Build();
|
|
var setLimits = new SetLimitsCommandEngine();
|
|
CommandBuilder.Builder("setBuildLimits", "Set build limits").Action((Action<int, int, int>)setLimits.SetLimits).Build();
|
|
GameEngineManager.AddGameEngine(setLimits);
|
|
|
|
CommandBuilder.Builder("freeScaling", "This command removes scaling restrictions on the selected block. Reselect block to apply.")
|
|
.Action(() =>
|
|
{
|
|
var blockID = Player.LocalPlayer.SelectedBlock;
|
|
if (blockID == BlockIDs.Invalid)
|
|
{
|
|
Logging.CommandLogWarning("You don't have any blocks in your hand.");
|
|
return;
|
|
}
|
|
FullGameFields._dataDb.GetValue<CubeListData>((int) blockID).scalingPermission =
|
|
ScalingPermission.NonUniform;
|
|
Logging.CommandLog("Free scaling enabled for " + blockID + " until the game is restarted. Reselect block to apply.");
|
|
}).Build();
|
|
|
|
CommandBuilder.Builder("setTweakLimit",
|
|
"Sets the limit on the tweakable stat on selected block. Usage: setTweakLimit [stat] [value]. Sets all stats to 1000 by default.")
|
|
.Action((string stat, int value) =>
|
|
{
|
|
var bl = Player.LocalPlayer.SelectedBlock;
|
|
if (bl == BlockIDs.Invalid)
|
|
{
|
|
Logging.CommandLogError("Select the block in the inventory first.");
|
|
return;
|
|
}
|
|
|
|
if (!FullGameFields._dataDb.TryGetValue<TweakableStatsData>((int)bl, out var data))
|
|
{
|
|
Logging.CommandLogError($"No tweakable stats found on {bl} (selected)");
|
|
return;
|
|
}
|
|
|
|
TweakPropertyInfo[] stats;
|
|
if (stat is null || stat.Length == 0)
|
|
{
|
|
stats = data.Stats;
|
|
}
|
|
else
|
|
{
|
|
if (!data.statsByName.TryGetValue(stat, out var statInfo))
|
|
{
|
|
Logging.CommandLogError($"Tweakable stat {stat} not found. Stats: {data.statsByName.Keys.Aggregate((a, b) => a + ", " + b)}");
|
|
return;
|
|
}
|
|
|
|
stats = new[] { statInfo };
|
|
}
|
|
|
|
foreach (var statInfo in stats)
|
|
{
|
|
statInfo.max = value == 0 ? 1000 : value;
|
|
statInfo.min = -1000;
|
|
}
|
|
|
|
Logging.CommandLog($"{(stat is null || stat.Length == 0 ? "All stats" : $"Stat {stat}")} max changed to {(value == 0 ? 1000 : value)} on {bl}");
|
|
}).Build();
|
|
|
|
//_mirrorModeEngine.Init();
|
|
UI.Init();
|
|
|
|
new Harmony("BuildTools").PatchAll(Assembly.GetExecutingAssembly());
|
|
}
|
|
|
|
private string GetBlockInfo()
|
|
{
|
|
if (GameState.IsBuildMode())
|
|
return GetBlockInfoInBuildMode();
|
|
if (GameState.IsSimulationMode())
|
|
return GetBodyInfoInSimMode();
|
|
|
|
return "Switching modes...";
|
|
}
|
|
|
|
private static string GetBlockInfoInBuildMode()
|
|
{
|
|
var block = Player.LocalPlayer.GetBlockLookedAt();
|
|
if (block == null) return GetWireInfoInBuildMode();
|
|
float3 pos = block.Position;
|
|
float3 rot = block.Rotation;
|
|
float3 scale = block.Scale;
|
|
return $"Block: {block.Type} at {pos.x:F} {pos.y:F} {pos.z:F}\n" +
|
|
$"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
|
|
$"- Color: {block.Color.Color} darkness: {block.Color.Darkness}\n" +
|
|
$"- Material: {block.Material}\n" +
|
|
$"- Scale: {scale.x:F} {scale.y:F} {scale.z:F}\n" +
|
|
$"- Label: {block.Label}\n" +
|
|
$"- ID: {block.Id}\n" +
|
|
(block.BlockGroup != null ? $"- Group: {block.BlockGroup.Id}\n" : "") +
|
|
$"- Mass: {block.Mass}";
|
|
}
|
|
|
|
private static string GetWireInfoInBuildMode()
|
|
{
|
|
var wire = Player.LocalPlayer.GetWireLookedAt();
|
|
if (wire == null) return "";
|
|
var startPos = wire.Start.Position;
|
|
var endPos = wire.End.Position;
|
|
return $"Wire with {wire.Id}\n" +
|
|
$"- From block {wire.Start.Type} at {startPos.x:F} {startPos.y:F} {startPos.z:F}\n" +
|
|
$"- at port {wire.StartPortName}\n" +
|
|
$"- To block {wire.End.Type} at {endPos.x:F} {endPos.y:F} {endPos.z:F}\n" +
|
|
$"- at port {wire.EndPortName}";
|
|
}
|
|
|
|
private static string GetBodyInfoInSimMode()
|
|
{
|
|
var body = Player.LocalPlayer.GetSimBodyLookedAt();
|
|
if (body == null) return GetBlockInfoInBuildMode();
|
|
float3 pos = body.Position;
|
|
float3 rot = body.Rotation;
|
|
float3 vel = body.Velocity;
|
|
float3 ave = body.AngularVelocity;
|
|
float3 com = body.CenterOfMass;
|
|
Cluster cluster = body.Cluster;
|
|
return $"Body at {pos.x:F} {pos.y:F} {pos.z:F}\n" +
|
|
$"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
|
|
$"- Velocity: {vel.x:F} {vel.y:F} {vel.z:F}\n" +
|
|
$"- Angular velocity: {ave.x:F} {ave.y:F} {ave.z:F}\n" +
|
|
$"- {(body.Static ? "Static body" : $"Center of mass: {com.x:F} {com.y:F} {com.z:F}")}\n" +
|
|
$"- Volume: {body.Volume:F}\n" +
|
|
$"- Chunk health: {body.CurrentHealth:F} / {body.InitialHealth:F} - Multiplier: {body.HealthMultiplier:F}\n" +
|
|
(cluster == null
|
|
? ""
|
|
: $"- Cluster health: {cluster.CurrentHealth:F} / {cluster.InitialHealth:F} - Multiplier: {cluster.HealthMultiplier:F}\n" +
|
|
$"- Cluster mass: {cluster.Mass}"
|
|
);
|
|
}
|
|
|
|
private string GetPlayerInfo()
|
|
{
|
|
var player = Player.LocalPlayer;
|
|
if (player == null) return "";
|
|
float3 pos = player.Position;
|
|
float3 rot = player.Rotation;
|
|
float3 vel = player.Velocity;
|
|
float3 ave = player.AngularVelocity;
|
|
return $"Player position: {pos.x:F} {pos.y:F} {pos.z:F}\n" +
|
|
$"- Rotation: {rot.x:F}° {rot.y:F}° {rot.z:F}°\n" +
|
|
$"- Velocity: {vel.x:F} {vel.y:F} {vel.z:F}\n" +
|
|
$"- Angular velocity: {ave.x:F} {ave.y:F} {ave.z:F}\n" +
|
|
$"- Health: {player.CurrentHealth:F} / {player.InitialHealth:F}";
|
|
}
|
|
|
|
private IEnumerable<SimBody> GetSimBodies(Block[] blocks)
|
|
=> blocks.Select(block => block.GetSimBody()).Where(block => !(block is null)).Distinct();
|
|
|
|
public override void OnApplicationQuit() => Main.Shutdown();
|
|
|
|
public override string Name => "BuildingTools";
|
|
public override string Version => "v1.1.0";
|
|
}
|
|
} |