diff --git a/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ScCoordWriter.cs b/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ScCoordWriter.cs new file mode 100644 index 00000000..1d68bd23 --- /dev/null +++ b/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ScCoordWriter.cs @@ -0,0 +1,420 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Sandbox.Game; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRage.Utils; +using VRageMath; + +namespace YourName.ModName.Data.Scripts.ScCoordWriter +{ + [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] + public class ScCoordWriter : MySessionComponentBase + { + public static ScCoordWriter Instance; + private ushort NetworkId; + private List TrackedItems = new List(); + private TextWriter Writer; + private bool Recording; + + private const int Version = 2; + private readonly string[] _columns = + { + "kind", "name", "owner", "faction", "factionColor", "entityId", "health", "position", "rotation", "gridSize" + }; + + private const string Extension = ".scc"; + private const string CommandPrefix = "/coordwriter"; + public string Usage = $"Usage: {CommandPrefix} [stop|start]"; + + private int TickCounter = 0; + + private class TrackedItem + { + public object item; + public int initialBlockCount; + public bool isVolumeExported; + + public TrackedItem(object item, int initialBlockCount = 1) + { + this.item = item; + this.initialBlockCount = initialBlockCount; + } + } + + public override void LoadData() + { + Instance = this; + NetworkId = 12493; + if (!MyAPIGateway.Utilities.IsDedicated) + { + MyAPIGateway.Utilities.MessageEnteredSender += HandleMessage; + } + else + { + MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(NetworkId, ReceivedPacket); + } + + MyAPIGateway.Entities.GetEntities(null, entity => + { + if (ShouldBeTracked(entity)) + { + var grid = entity as IMyCubeGrid; + if (grid != null) + { + var blocks = new List(); + grid.GetBlocks(blocks); + TrackedItems.Add(new TrackedItem(grid, blocks.Count)); + } + } + return false; + }); + MyAPIGateway.Entities.OnEntityAdd += OnEntityAdd; + MyAPIGateway.Entities.OnEntityRemove += OnEntityRemove; + } + + private void OnEntityAdd(IMyEntity entity) + { + if (ShouldBeTracked(entity)) + { + var grid = entity as IMyCubeGrid; + if (grid != null) + { + var blocks = new List(); + grid.GetBlocks(blocks); + TrackedItems.Add(new TrackedItem(grid, blocks.Count)); + } + } + } + + private void OnEntityRemove(IMyEntity entity) + { + for (var i = 0; i < TrackedItems.Count; ++i) + { + var cur = TrackedItems[i]; + var grid = cur.item as IMyCubeGrid; + if (grid != null && grid.EntityId == entity.EntityId) + { + TrackedItems.RemoveAt(i); + break; + } + } + } + + private bool ShouldBeTracked(IMyEntity entity) + { + var grid = entity as IMyCubeGrid; + return grid != null && !grid.IsStatic && grid.Physics != null; + } + + protected override void UnloadData() + { + Writer?.Close(); + TrackedItems?.Clear(); + if (!MyAPIGateway.Utilities.IsDedicated) + { + MyAPIGateway.Utilities.MessageEnteredSender -= HandleMessage; + } + else + { + MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(NetworkId, ReceivedPacket); + } + MyAPIGateway.Entities.OnEntityAdd -= OnEntityAdd; + MyAPIGateway.Entities.OnEntityRemove -= OnEntityRemove; + } + + public void Start() + { + var fileName = $"{DateTime.Now:dd-MM-yyyy HHmm}{Extension}"; + + try + { + Writer = MyAPIGateway.Utilities.WriteFileInWorldStorage(fileName, typeof(ScCoordWriter)); + Writer.NewLine = "\n"; + MyVisualScriptLogicProvider.SendChatMessage("Global grid tracker file created"); + Writer.WriteLine($"version {Version}"); + Writer.WriteLine(string.Join(",", _columns)); + } + catch (Exception ex) + { + MyLog.Default.WriteLine("Failed to create grid tracker file."); + MyVisualScriptLogicProvider.SendChatMessage("Failed to create grid tracker file."); + MyLog.Default.WriteLine(ex); + } + + if (TrackedItems != null) + { + foreach (var element in TrackedItems) + { + element.isVolumeExported = false; + } + } + + Recording = true; + MyAPIGateway.Multiplayer.SendMessageToServer(NetworkId, new byte[] { 1 }); + MyAPIGateway.Utilities.ShowNotification("Recording started."); + } + + public void Stop() + { + Recording = false; + MyAPIGateway.Multiplayer.SendMessageToServer(NetworkId, new byte[] { 0 }); + MyAPIGateway.Utilities.ShowNotification("Recording ended."); + } + + public override void UpdateAfterSimulation() + { + if (!Recording) return; + if (TrackedItems == null) + { + MyVisualScriptLogicProvider.SendChatMessage("TrackedItems is null"); + return; + } + + if (TickCounter++ < 60) { return; } + TickCounter = 0; + + Writer.WriteLine($"start_block,{DateTime.Now}"); + TrackedItems.ForEach(element => + { + if (element.item == null) + { + MyLog.Default.WriteLine("null item in TrackedItems"); + return; + } + + var grid = element.item as IMyCubeGrid; + var owner = GetGridOwner(grid); + var factionName = GetFactionName(owner); + var factionColor = GetFactionColor(owner); + + // Use the grid's world matrix for position and rotation + MatrixD worldMatrix = grid.WorldMatrix; + Vector3D position = grid.Physics.CenterOfMassWorld; + Quaternion rotation = Quaternion.CreateFromForwardUp(worldMatrix.Forward, worldMatrix.Up); + + var blockList = new List(); + grid.GetBlocks(blockList); + var currentBlockCount = blockList.Count; + if (currentBlockCount > element.initialBlockCount) + { + element.initialBlockCount = currentBlockCount; + } + var healthPercent = (float)currentBlockCount / element.initialBlockCount; + + // Determine if the grid is small or large + var gridSize = grid.GridSizeEnum == MyCubeSize.Small ? "Small" : "Large"; + + Writer.WriteLine($"grid,{grid.CustomName},{owner?.DisplayName ?? "Unowned"},{factionName},{factionColor},{grid.EntityId},{SmallDouble(healthPercent)},{SmallVector3D(position)},{SmallQuaternion(rotation)},{gridSize}"); + + if (!element.isVolumeExported) + { + var volume = ConvertToBase64BinaryVolume(grid); + Writer.WriteLine($"volume,{grid.EntityId},{volume}"); + element.isVolumeExported = true; + } + }); + Writer.Flush(); + } + + public string SmallQuaternion(Quaternion q) + { + return + $"{SmallDouble(q.X)} {SmallDouble(q.Y)} {SmallDouble(q.Z)} {SmallDouble(q.W)}"; + } + public string SmallVector3D(Vector3D v) + { + + return $"{SmallDouble(v.X)} {SmallDouble(v.Y)} {SmallDouble(v.Z)}"; + } + public string SmallDouble(double value) + { + const int decimalPlaces = 2; + return value.ToString($"F{decimalPlaces}"); + } + + public void HandleMessage(ulong sender, string messageText, ref bool sendToOthers) + { + if (!messageText.StartsWith(CommandPrefix)) return; + sendToOthers = false; + + var args = messageText.Split(' '); + + if (args.Length != 2) + { + return; + + } + + switch (args[1]) + { + case "start": + Start(); + break; + case "stop": + Stop(); + break; + default: + { + var error = $"[{nameof(ScCoordWriter)}] Unknown command '{args[1]}'"; + MyLog.Default.WriteLine(error); + MyAPIGateway.Utilities.ShowMessage($"[{nameof(ScCoordWriter)}]", error); + MyAPIGateway.Utilities.ShowMessage($"[{nameof(ScCoordWriter)}]", Usage); + } + break; + } + } + + public void ReceivedPacket(ushort channelId, byte[] data, ulong steamSenderId, bool isSenderServer) + { + if (data != null && data.Length == 1) + { + Recording = data[0] == 1; + if (Recording) + { + Start(); + } + else + { + Stop(); + } + } + } + + private string GetFactionName(IMyIdentity player) + { + if (player == null) return "Unowned"; + IMyFaction playerFaction = MyAPIGateway.Session.Factions.TryGetPlayerFaction(player.IdentityId); + return playerFaction != null ? playerFaction.Name : "Unowned"; + } + + private string GetFactionColor(IMyIdentity owner) + { + if (owner == null) return SmallVector3D(Vector3D.Zero); + + var faction = MyAPIGateway.Session.Factions.TryGetPlayerFaction(owner.IdentityId); + if (faction != null) + { + // Example, replace with actual way to get faction color if available + + return SmallVector3D(faction.CustomColor); + } + return SmallVector3D(Vector3D.Zero); // Default color if no faction or no color defined + } + + + public IMyIdentity GetGridOwner(IMyCubeGrid grid) + { + IMyIdentity owner = null; + if (grid.BigOwners.Count > 0) + { + var identities = new List(); + MyAPIGateway.Players.GetAllIdentites(identities, id => id.IdentityId == grid.BigOwners[0]); + if (identities.Count > 0) + { + owner = identities[0]; + } + } + return owner; + } + + public string ConvertToBase64BinaryVolume(IMyCubeGrid grid) + { + // Get grid dimensions + var extents = grid.Max - grid.Min + Vector3I.One; + int width = extents.X; + int height = extents.Y; + int depth = extents.Z; + + // Calculate number of bytes needed to store the volume + int numBytes = (width * height * depth + 7) / 8; + + // Create byte array to store binary volume + byte[] binaryVolume = new byte[numBytes]; + + // Iterate over grid cells + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Calculate index in byte array + int byteIndex = z * width * height + y * width + x; + int bytePosition = byteIndex % 8; + + // Check if block is present at the cell + var block = grid.GetCubeBlock(new Vector3I(x, y, z) + grid.Min); + bool blockPresent = block != null; + + // Set corresponding bit in byte + if (blockPresent) + { + binaryVolume[byteIndex / 8] |= (byte)(1 << (7 - bytePosition)); // Invert the byte position + } + } + } + } + + // Create header with grid extents + byte[] header = BitConverter.GetBytes(width) + .Concat(BitConverter.GetBytes(height)) + .Concat(BitConverter.GetBytes(depth)) + .ToArray(); + + // Combine header and binary volume + byte[] result = header.Concat(binaryVolume).ToArray(); + + // Compress the result using RLE + byte[] compressedData = Compress(result); + + // Convert to base64 string + string base64String = Convert.ToBase64String(compressedData); + + return base64String; + } + + private byte[] Compress(byte[] data) + { + List compressedData = new List(); + + int count = 1; + byte currentByte = data[0]; + + for (int i = 1; i < data.Length; i++) + { + if (data[i] == currentByte) + { + count++; + if (count == 256) + { + // Add the current byte and max count (255), reset the count + compressedData.Add(currentByte); + compressedData.Add(255); + count = 1; + } + } + else + { + compressedData.Add(currentByte); + compressedData.Add((byte)count); + count = 1; + currentByte = data[i]; + } + } + + // Add the last byte and its count + compressedData.Add(currentByte); + compressedData.Add((byte)count); + + return compressedData.ToArray(); + } + } +} diff --git a/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ULTRALogger.cs b/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ULTRALogger.cs new file mode 100644 index 00000000..e70beaba --- /dev/null +++ b/Utility Mods/Development/ULTRALogger/Data/Scripts/Logger/ULTRALogger.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Sandbox.Game; +using Sandbox.ModAPI; +using VRage.Game.Components; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRage.Utils; +using VRageMath; + +namespace ULTRALogger +{ + [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] + public class ULTRALogger : MySessionComponentBase + { + public static ULTRALogger Instance; + private TextWriter + _gridsWriter, + _playersWriter, + _projectilesWriter; + + private const string Extension = ".log"; + private bool _isRecording; + private DateTime _last; + private Vector3D _badVector = new Vector3D(double.NaN); + private HashSet _playerIdentities = new HashSet(); + + #region common + + private void CheckTime() + { + // i just feel like it + if ((DateTime.Now - _last).TotalSeconds != 0) + { + _gridsWriter.WriteLine('\n'); + _last = DateTime.Now; + } + } + + private string ShorterPositionString(Vector3D position) => position != _badVector ? $"(X = {position.X:#0.#}, Y = {position.Y:#0.#}, Z {position.Z:#0.#}) " : ""; + + private string Timestamp() => $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]"; + + #endregion + + public override void LoadData() + { + Instance = this; + StartLogging(); + MyAPIGateway.Entities.OnEntityAdd += OnEntityAdd; + //MyVisualScriptLogicProvider.PlayerConnected + MyVisualScriptLogicProvider.PlayerRespawnRequest += OnPlayerRespawn; + MyAPIGateway.Entities.OnEntityRemove += OnEntityRemove; + + MyAPIGateway.Players.GetAllIdentites(null, b => + { + if (!_playerIdentities.Contains(b.IdentityId)) + _playerIdentities.Add(b.IdentityId); + return true; + }); + } + + protected override void UnloadData() + { + StopLogging(); + MyAPIGateway.Entities.OnEntityAdd -= OnEntityAdd; + MyAPIGateway.Entities.OnEntityRemove -= OnEntityRemove; + Instance = null; + } + + + + private void StartLogging() + { + var fileName = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}{Extension}"; + string ascii = " __ __ __ .___________..______ ___ __ ______ _______ _______ _______ .______ \r\n| | | | | | | || _ \\ / \\ | | / __ \\ / _____| / _____|| ____|| _ \\ \r\n| | | | | | `---| |----`| |_) | / ^ \\ | | | | | | | | __ | | __ | |__ | |_) | \r\n| | | | | | | | | / / /_\\ \\ | | | | | | | | |_ | | | |_ | | __| | / \r\n| `--' | | `----. | | | |\\ \\----./ _____ \\ | `----.| `--' | | |__| | | |__| | | |____ | |\\ \\----.\r\n \\______/ |_______| |__| | _| `._____/__/ \\__\\ |_______| \\______/ \\______| \\______| |_______|| _| `._____|\r\n\n"; + try + { + _gridsWriter = MyAPIGateway.Utilities.WriteFileInWorldStorage("ULTRALog_GRIDS", typeof(ULTRALogger)); + _gridsWriter.Write(ascii); + _gridsWriter.WriteLine("GRID LOG"); + + _playersWriter = MyAPIGateway.Utilities.WriteFileInWorldStorage("ULTRALog_PLAYERS", typeof (ULTRALogger)); + _playersWriter.Write(ascii); + _playersWriter.WriteLine("PLAYER LOG"); + + // TODO: wc stuff. laterrrr + + _isRecording = true; + } + catch (Exception ex) + { + MyLog.Default.WriteLineAndConsole($"[ULTRALogger] Error creating log file: {ex.Message}\n{ex.StackTrace}"); + } + } + + private void StopLogging() + { + _isRecording = false; + _gridsWriter?.Close(); + _playersWriter?.Close(); + _projectilesWriter?.Close(); + MyAPIGateway.Utilities.ShowNotification("ULTRALogger stopped."); + } + + #region grids + + private void OnEntityAdd(IMyEntity entity) + { + if (_isRecording) + { + CheckTime(); + var grid = entity as IMyCubeGrid; + + if (grid != null) + { + var owner = MyAPIGateway.Players.TryGetIdentityId(grid.BigOwners[0]).DisplayName; + grid.OnBlockAdded += OnBlockAdded; + grid.OnGridBlockDamaged += OnDamaged; + grid.OnBlockRemoved += OnBlockRemoved; + _playersWriter.WriteLine($"{Timestamp()} {owner} added new grid: \"{grid.CustomName}\" at {ShorterPositionString(grid.GetPosition())}"); + _playersWriter.Flush(); + } + } + } + private void OnEntityRemove(IMyEntity entity) + { + if (_isRecording) + { + CheckTime(); + var grid = entity as IMyCubeGrid; + if (grid != null) + { + // do i need this? + grid.OnBlockAdded -= OnBlockAdded; + grid.OnBlockRemoved -= OnBlockRemoved; + grid.OnGridBlockDamaged -= OnDamaged; + _gridsWriter.WriteLine($"{Timestamp()} Removed grid: \"{grid.CustomName}\" at {ShorterPositionString(grid.GetPosition())}"); + _gridsWriter.Flush(); + } + } + } + + private void OnDamaged(IMySlimBlock block, float whatever, MyHitInfo? hit, long presumablyEntityID) + { + if (_isRecording) + { + CheckTime(); + string + parent = block?.CubeGrid.CustomName, + blockType = block.FatBlock?.BlockDefinition.SubtypeId ?? "slim", + position = block.FatBlock != null ? "at " + ShorterPositionString((hit.HasValue ? hit.Value.Position : _badVector)) : ""; + + _gridsWriter.WriteLine($"{Timestamp()} {blockType} block {position}on parent grid \"{parent}\""); + _gridsWriter.Flush(); + } + } + + #endregion + + #region players + + private void OnPlayerRespawn(long identityId) + { + var player = MyAPIGateway.Players.TryGetIdentityId(identityId); + if (player != null) + { + if (_playerIdentities.Contains(identityId)) + _playersWriter.WriteLine($"{Timestamp()} {MyAPIGateway.Players.TryGetIdentityId(identityId).DisplayName} respawned at {ShorterPositionString(player.GetPosition())}"); + else + { + _playersWriter.WriteLine($"{Timestamp()} {MyAPIGateway.Players.TryGetIdentityId(identityId).DisplayName} joined the server and respawned at {ShorterPositionString(player.GetPosition())}"); + _playerIdentities.Add(identityId); + } + _playersWriter.Flush(); + } + } + + private void OnBlockAdded(IMySlimBlock block) + { + if (_isRecording) + { + CheckTime(); + string + builder = MyAPIGateway.Players.TryGetIdentityId(block.BuiltBy).DisplayName, + parent = block?.CubeGrid.CustomName, + blockType = block.FatBlock?.BlockDefinition.SubtypeId ?? "slim", + position = block.FatBlock != null ? "at " + ShorterPositionString(block.FatBlock.GetPosition()) : ""; + + _gridsWriter.WriteLine($"{Timestamp()} {builder} added new {blockType} block {position}on parent grid \"{parent}\""); + _gridsWriter.Flush(); + } + } + + private void OnBlockRemoved(IMySlimBlock block) + { + if (_isRecording) + { + CheckTime(); + string + parent = block?.CubeGrid.CustomName, + blockType = block.FatBlock?.BlockDefinition.SubtypeId ?? "slim", + position = block.FatBlock != null ? "at " + ShorterPositionString(block.FatBlock.GetPosition()) : ""; + + _gridsWriter.WriteLine($"{Timestamp()} Removed {blockType} block {position}from parent grid \"{parent}\""); + _gridsWriter.Flush(); + } + } + + #endregion + } +} diff --git a/Weapon Mods/Development/REEECore-Dev/file.txt b/Weapon Mods/Development/REEECore-Dev/file.txt new file mode 100644 index 00000000..e69de29b