using System; using System.Collections.Generic; using System.Linq; using Gamecraft.Wires.ChannelsCommon; using GamecraftModdingAPI; using GamecraftModdingAPI.Blocks; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Engines; using GamecraftModdingAPI.Players; using GamecraftModdingAPI.Utility; using HarmonyLib; using IllusionPlugin; using RobocraftX.Character.Weapons; using RobocraftX.CommandLine.Custom; using RobocraftX.Common.Players; using Svelto.ECS; using Svelto.ECS.Internal; using Unity.Mathematics; using Main = GamecraftModdingAPI.Main; namespace BlockMod { public class BlockMod : IPlugin { public void OnApplicationStart() { Main.Init(); GameClient.SetDebugInfo("BlockModInfo", GetBlockInfo); Block[] blocks = new Block[0]; Block refBlock = null; CommandBuilder.Builder("scaleBlocks", "Scales the blocks you're looking at, 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." + " The command remembers the cluster of blocks, use forgetBlocks to forget it.") .Action((scaleX, scaleY, scaleZ) => { if(CheckNoBlocks(blocks)) return; // ReSharper disable once PossibleNullReferenceException float3 reference = refBlock.Position; float3 scale = new float3(scaleX, scaleY, scaleZ); foreach (var block in blocks) { block.Scale *= scale; block.Position = reference + (block.Position - reference) * scale; } Logging.CommandLog("Blocks scaled."); }).Build(); CommandBuilder.Builder("scaleIndividually", "Scales the blocks you're looking at, but doesn't move them." + "The scale is relative, 1 means no change. Look at a block previously scaled to scale all of the blocks that were connected to it.") .Action((scaleX, scaleY, scaleZ) => { if(CheckNoBlocks(blocks)) return; float3 scale = new float3(scaleX, scaleY, scaleZ); foreach (var block in blocks) block.Scale *= scale; }).Build(); CommandBuilder.Builder("moveBlocks", "Moves the blocks around.") .Action((x, y, z) => { if (CheckNoBlocks(blocks)) return; 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); }).Build(); CommandBuilder.Builder("colorBlocks", "Colors the blocks you're looking at") .Action((color, darkness) => { if(CheckNoBlocks(blocks)) return; if (!Enum.TryParse(color, true, out BlockColors clr)) { Logging.CommandLogWarning("Color " + color + " not found"); } foreach (var block in blocks) block.Color = new BlockColor {Color = clr, Darkness = darkness}; }).Build(); CommandBuilder.Builder("selectBlocksLookedAt", "Selects blocks (1 or more) to change. Only works in build mode, however the blocks can be changed afterwards." + " Paramter: whether one (true) or all connected (false) blocks should be selected.") .Action(single => { refBlock = new Player(PlayerType.Local).GetBlockLookedAt(); blocks = single ? new[] {refBlock} : refBlock?.GetConnectedCubes() ?? new Block[0]; Logging.CommandLog(blocks == null ? "Selection cleared." : blocks.Length + " blocks selected."); }).Build(); CommandBuilder.Builder("selectBlocksWithID", "Selects blocks with a specific object ID.") .Action(id => blocks = (refBlock = ObjectIdentifier.GetByID(id).FirstOrDefault())?.GetConnectedCubes() ?? new Block[0]).Build(); ConsoleCommands.RegisterWithChannel("selectBlocksFromChannel", id => { blocks = ObjectIdentifier.GetBySimID(id).SelectMany(block => block.GetConnectedCubes()).ToArray(); }, BinaryChannelUtilities.ChannelType.Object); CommandBuilder.Builder("pushBlocks", "Adds velocity to the selected blocks. Only works in simulation.") .Action((x, y, z) => { foreach (var block in GetSimBodies(blocks)) block.Velocity += new float3(x, y, z); }).Build(); CommandBuilder.Builder("pushRotateBlocks", "Adds angular velocity to the selected blocks. Only works in simulation.") .Action((x, y, z) => { foreach (var block in GetSimBodies(blocks)) block.AngularVelocity += new float3(x, y, z); }).Build(); CommandBuilder.Builder("pushPlayer", "Adds velocity to the player.") .Action((x, y, z) => { new Player(PlayerType.Local).Velocity += new float3(x, y, z); }).Build(); CommandBuilder.Builder("pushRotatePlayer", "Adds angular velocity to the player.") .Action((x, y, z) => { new Player(PlayerType.Local).AngularVelocity += new float3(x, y, z); }).Build(); GameEngineManager.AddGameEngine(new Test()); } private class Test : IApiEngine { public void Ready() { try { CommandBuilder.Builder("weaponTest").Action(() => { var type = AccessTools.TypeByName("RobocraftX.Character.Weapons.CharacterEquippedWeaponStruct"); var dict = QueryEntitiesAndIndexInternal( new EGID(new Player(PlayerType.Local).Id, PlayersExclusiveGroups.LocalPlayers), out uint i, type); var dtype = typeof(ITypeSafeDictionary<>).MakeGenericType(type); var obj = Convert.ChangeType(dict, dtype); Array arr = (Array) AccessTools.PropertyGetter(dtype, "unsafeValues") .Invoke(obj, new object[0]); foreach (var element in arr) element.GetType().GetField("equippedWeaponType") .SetValue(element, WeaponType.PISTOL); }).Build(); } catch { // ignored } } private ITypeSafeDictionary QueryEntitiesAndIndexInternal(EGID entityGID, out uint index, Type type) { index = 0U; ITypeSafeDictionary typeSafeDictionary; if (!QueryEntityDictionary(entityGID.groupID, out typeSafeDictionary, type)) return null; return !typeSafeDictionary.TryFindIndex(entityGID.entityID, out index) ? null : typeSafeDictionary; } private bool QueryEntityDictionary( uint group, out ITypeSafeDictionary typeSafeDictionary, Type type) { object[] ps = {group, type, null}; bool ret = (bool) AccessTools.Method("Svelto.ECS.EntitiesDB:UnsafeQueryEntityDictionary") .Invoke(entitiesDB, ps); typeSafeDictionary = (ITypeSafeDictionary) ps[2]; return ret; } public EntitiesDB entitiesDB { get; set; } public void Dispose() { } public string Name { get; } = "TestEngine"; public bool isRemovable { get; } = true; } private string GetBlockInfo() { if (GameState.IsBuildMode()) { var block = new Player(PlayerType.Local).GetBlockLookedAt(); 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" + $"- Scale: {scale.x:F} {scale.y:F} {scale.z:F}\n" + $"- Label: {block.Label}"; } if (GameState.IsSimulationMode()) { var body = new Player(PlayerType.Local).GetSimBodyLookedAt(); float3 pos = body.Position; float3 rot = body.Rotation; float3 vel = body.Velocity; float3 ave = body.AngularVelocity; float3 com = body.CenterOfMass; 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" : $"Mass: {body.Mass:F} center: {com.x:F} {com.y:F} {com.z:F}")}"; } return "Switching modes..."; } private bool CheckNoBlocks(Block[] blocks) { if (blocks.Length == 0) { Logging.CommandLogWarning("No blocks selected. Use selectBlocks first."); return true; } return false; } public IEnumerable GetSimBodies(Block[] blocks) => blocks.Select(block => block.GetSimBody()).Distinct(); public void OnApplicationQuit() { } public void OnLevelWasLoaded(int level) { } public void OnLevelWasInitialized(int level) { } public void OnUpdate() { } public void OnFixedUpdate() { } public string Name { get; } = "BlockMod"; public string Version { get; } = "v1.0.0"; } }