using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Sandbox.Game.Entities; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame; using VRageMath; using Terminal = Sandbox.ModAPI.Ingame.IMyTerminalBlock; using SlimBlock = Sandbox.Game.Entities.Cube.MySlimBlock; using ShipConnector = Sandbox.Game.Entities.Cube.MyShipConnector; namespace XFE.SeAgent.Plugin.Game { public sealed partial class GameDebugApi { private JObject ListGrids(JObject args) { var grids = MyEntities.GetEntities().OfType().Where(g => !g.Closed).OrderBy(g => g.EntityId).ToList(); string name = (string)args["name"]; if (!string.IsNullOrEmpty(name)) grids = grids.Where(g => (g.DisplayName ?? "").IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0).ToList(); int offset = Math.Max(0, (int?)args["offset"] ?? 0); var items = new JArray(grids.Skip(offset).Take(Limit(args)).Select(DescribeGrid)); return new JObject { ["grids"] = items, ["total"] = grids.Count, ["offset"] = offset, ["truncated"] = offset + items.Count < grids.Count }; } private static JObject DescribeGrid(MyCubeGrid grid) { var result = new JObject { ["entityId"] = Sid(grid.EntityId), ["name"] = grid.DisplayName, ["isStatic"] = grid.IsStatic, ["gridSize"] = grid.GridSize, ["blockCount"] = grid.GetBlocks().Count, ["pose"] = Pose(grid.WorldMatrix), ["bounds"] = Bounds(grid.PositionComp.WorldAABB) }; if (grid.Physics != null) { result["linearVelocity"] = Vec(grid.Physics.LinearVelocity); result["angularVelocity"] = Vec(grid.Physics.AngularVelocity); result["speed"] = grid.Physics.LinearVelocity.Length(); } return result; } private static JObject DescribeGridWithIntegrity(MyCubeGrid grid) { var result = DescribeGrid(grid); // Include armor as well as terminal blocks. Bound work on unusually large grids; // ordinary grids.list queries do not perform this scan. const int maximumBlocks = 20000; int inspected = 0, damaged = 0, deformed = 0, incomplete = 0, affected = 0; double integrity = 0, buildIntegrity = 0, maxIntegrity = 0, currentDamage = 0, pendingDamage = 0; var damageLocations = new JArray(); foreach (var block in grid.GetBlocks()) { if (inspected >= maximumBlocks) break; inspected++; integrity += block.Integrity; buildIntegrity += block.BuildIntegrity; maxIntegrity += block.MaxIntegrity; currentDamage += block.CurrentDamage; pendingDamage += block.AccumulatedDamage; bool isDamaged = block.CurrentDamage > 0 || block.AccumulatedDamage > 0; if (isDamaged) damaged++; if (block.HasDeformation) deformed++; if (block.BuildIntegrity < block.MaxIntegrity) incomplete++; if (isDamaged || block.HasDeformation) affected++; if ((isDamaged || block.HasDeformation) && damageLocations.Count < 32) { var detail = DescribeIntegrity(block); detail["positionInGrid"] = Vec(block.Position); damageLocations.Add(detail); } } result["integrity"] = new JObject { ["inspectedBlockCount"] = inspected, ["truncated"] = inspected < grid.GetBlocks().Count, ["current"] = integrity, ["build"] = buildIntegrity, ["maximum"] = maxIntegrity, ["currentDamage"] = currentDamage, ["accumulatedDamage"] = pendingDamage, ["damagedBlockCount"] = damaged, ["deformedBlockCount"] = deformed, ["incompleteBlockCount"] = incomplete, ["damageLocations"] = damageLocations, ["damageLocationsTruncated"] = affected > damageLocations.Count }; return result; } private static JObject DescribeIntegrity(SlimBlock block) { return new JObject { ["current"] = block.Integrity, ["build"] = block.BuildIntegrity, ["maximum"] = block.MaxIntegrity, ["currentDamage"] = block.CurrentDamage, ["accumulatedDamage"] = block.AccumulatedDamage, ["hasDeformation"] = block.HasDeformation }; } private IEnumerable SelectBlocks(JObject args) { if (args["entityIds"] is JArray ids) { if (ids.Count > 256) throw new ArgumentException("entityIds accepts at most 256 blocks."); return ids.Select(id => Block(Id(new JObject { ["id"] = id.DeepClone() }, "id"))).Distinct().ToList(); } IEnumerable grids = args["gridId"] != null ? new[] { Grid(Id(args, "gridId")) } : MyEntities.GetEntities().OfType(); IEnumerable blocks = grids.Where(g => !g.Closed).SelectMany(g => g.GetFatBlocks().OfType()).Where(b => !b.Closed); string name = (string)args["name"], type = (string)args["type"]; if (!string.IsNullOrEmpty(name)) blocks = blocks.Where(b => (b.CustomName ?? "").IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0); if (!string.IsNullOrEmpty(type)) blocks = blocks.Where(b => b.BlockDefinition.TypeIdString.IndexOf(type, StringComparison.OrdinalIgnoreCase) >= 0 || b.BlockDefinition.SubtypeName.IndexOf(type, StringComparison.OrdinalIgnoreCase) >= 0); return blocks.OrderBy(b => b.EntityId); } private JObject ListBlocks(JObject args) { var blocks = SelectBlocks(args).ToList(); int offset = Math.Max(0, (int?)args["offset"] ?? 0); var array = new JArray(blocks.Skip(offset).Take(Limit(args)).Select(b => DescribeBlock(b, false))); return new JObject { ["blocks"] = array, ["total"] = blocks.Count, ["offset"] = offset, ["truncated"] = offset + array.Count < blocks.Count }; } private static JObject DescribeBlock(Terminal block, bool details) { var result = new JObject { ["entityId"] = Sid(block.EntityId), ["gridId"] = Sid(block.CubeGrid.EntityId), ["name"] = block.CustomName, ["type"] = block.BlockDefinition.TypeIdString, ["subtype"] = block.BlockDefinition.SubtypeName, ["functional"] = block.IsFunctional, ["working"] = block.IsWorking, ["ownerId"] = Sid(block.OwnerId), ["positionInGrid"] = Vec(block.Position), ["pose"] = Pose(block.WorldMatrix), ["bounds"] = Bounds(block.WorldAABB), ["inventoryCount"] = block.InventoryCount }; if (block is IMyFunctionalBlock functional) result["enabled"] = functional.Enabled; var slim = (block.CubeGrid as MyCubeGrid)?.GetCubeBlock(block.Position); if (slim != null) result["integrity"] = DescribeIntegrity(slim); if (details) { result["customData"] = block.CustomData ?? ""; result["detailedInfo"] = Clip(block.DetailedInfo, 32768); result["customInfo"] = Clip(block.CustomInfo, 32768); } return result; } private JObject Telemetry(JObject args) { int limit = Limit(args, 256, 1024); var selected = SelectBlocks(args).ToList(); var blocks = selected.Take(limit).ToList(); var result = new JArray(); foreach (var block in blocks) { var item = DescribeBlock(block, false); try { AddTelemetry(item, block, (bool?)args["includeInventoryItems"] ?? true, (bool?)args["includeScreens"] ?? true); } catch (Exception error) { item["telemetryError"] = error.GetType().Name + ": " + error.Message; } result.Add(item); } var grids = blocks.Select(b => b.CubeGrid.EntityId).Distinct().Select(id => DescribeGridWithIntegrity(Grid(id))); return new JObject { ["utc"] = DateTime.UtcNow, ["frame"] = RequireWorld().GameplayFrameCounter, ["grids"] = new JArray(grids), ["blocks"] = result, ["total"] = selected.Count, ["truncated"] = selected.Count > limit }; } private static void AddTelemetry(JObject item, Terminal block, bool includeInventoryItems, bool includeScreens) { if (block is IMyConveyorSorter sorter) item["sorter"] = DescribeSorter(sorter); if (block is IMyBatteryBlock battery) item["battery"] = new JObject { ["storedMWh"] = battery.CurrentStoredPower, ["maxStoredMWh"] = battery.MaxStoredPower, ["inputMW"] = battery.CurrentInput, ["outputMW"] = battery.CurrentOutput, ["chargeMode"] = battery.ChargeMode.ToString(), ["charging"] = battery.IsCharging }; if (block is IMyThrust thrust) item["thrust"] = new JObject { ["currentN"] = thrust.CurrentThrust, ["maximumN"] = thrust.MaxThrust, ["maxEffectiveN"] = thrust.MaxEffectiveThrust, ["overrideN"] = thrust.ThrustOverride, ["overrideRatio"] = thrust.ThrustOverridePercentage, ["gridDirection"] = Vec(thrust.GridThrustDirection), ["forceDirection"] = Vec(thrust.WorldMatrix.Backward) }; if (block is IMyGyro gyro) item["gyro"] = new JObject { ["override"] = gyro.GyroOverride, ["power"] = gyro.GyroPower, ["yaw"] = gyro.Yaw, ["pitch"] = gyro.Pitch, ["roll"] = gyro.Roll }; if (block is IMyShipConnector connector) { var status = connector.Status; var value = new JObject { ["status"] = status.ToString(), ["connected"] = status == MyShipConnectorStatus.Connected, ["connectable"] = status == MyShipConnectorStatus.Connectable, ["otherConnectorId"] = connector.OtherConnector == null ? null : Sid(connector.OtherConnector.EntityId), ["throwOut"] = connector.ThrowOut, ["collectAll"] = connector.CollectAll, ["pullStrength"] = connector.PullStrength }; item["connector"] = value; if (block is ShipConnector actual) { var point = actual.ConstraintPositionWorld(); value["constraintPosition"] = Vec(point); value["inConstraint"] = actual.InConstraint; value["magnetized"] = actual.InConstraint && status != MyShipConnectorStatus.Connected; value["isSmallConnector"] = actual.IsSmallConnector; value["tradingEnabled"] = actual.TradingEnabled.Value; value["protectedFromLockingByTrading"] = actual.IsProtectedFromLockingByTrading(); var other = actual.Other; value["constraintOtherConnectorId"] = other == null ? null : Sid(other.EntityId); if (other != null) { var otherPoint = other.ConstraintPositionWorld(); value["otherConstraintPosition"] = Vec(otherPoint); value["constraintDistance"] = Vector3D.Distance(point, otherPoint); } } } if (block is IMyCameraBlock camera) item["camera"] = new JObject { ["enabledRaycast"] = camera.EnableRaycast, ["availableScanRange"] = camera.AvailableScanRange, ["coneLimitDegrees"] = camera.RaycastConeLimit, ["distanceLimit"] = camera.RaycastDistanceLimit }; if (block is IMyGasTank tank) item["gasTank"] = new JObject { ["capacity"] = tank.Capacity, ["filledRatio"] = tank.FilledRatio, ["stockpile"] = tank.Stockpile }; if (block is IMyShipController controller) { var velocities = controller.GetShipVelocities(); var mass = controller.CalculateShipMass(); item["flight"] = new JObject { ["linearVelocity"] = Vec(velocities.LinearVelocity), ["angularVelocity"] = Vec(velocities.AngularVelocity), ["speed"] = controller.GetShipSpeed(), ["naturalGravity"] = Vec(controller.GetNaturalGravity()), ["artificialGravity"] = Vec(controller.GetArtificialGravity()), ["totalMassKg"] = mass.TotalMass, ["physicalMassKg"] = mass.PhysicalMass, ["baseMassKg"] = mass.BaseMass, ["centerOfMass"] = Vec(controller.CenterOfMass), ["dampeners"] = controller.DampenersOverride, ["underControl"] = controller.IsUnderControl, ["controlThrusters"] = controller.ControlThrusters, ["moveIndicator"] = Vec(controller.MoveIndicator), ["rotationIndicator"] = new JObject { ["x"] = controller.RotationIndicator.X, ["y"] = controller.RotationIndicator.Y }, ["rollIndicator"] = controller.RollIndicator }; } if (block is Sandbox.ModAPI.IMyProgrammableBlock pb) { var instance = PbField(pb, "m_instance") as Sandbox.ModAPI.IMyGridProgram; item["programmableBlock"] = new JObject { ["sha256"] = Sha(pb.ProgramData), ["compileErrors"] = pb.HasCompileErrors, ["hasInstance"] = instance != null, ["runtime"] = instance == null ? null : Runtime(instance.Runtime), ["echo"] = Clip(PbField(pb, "m_echoOutput")?.ToString(), 8192), ["terminationReason"] = PbField(pb, "m_terminationReason")?.ToString() }; } if (block.HasInventory) { var inventories = new JArray(); for (int i = 0; i < Math.Min(block.InventoryCount, 16); i++) { var inventory = block.GetInventory(i); var inv = new JObject { ["index"] = i, ["massKg"] = (double)inventory.CurrentMass, ["volumeM3"] = (double)inventory.CurrentVolume, ["maxVolumeM3"] = (double)inventory.MaxVolume, ["itemCount"] = inventory.ItemCount }; if (includeInventoryItems) { var items = new List(); inventory.GetItems(items, null); inv["items"] = new JArray(items.Take(128).Select(value => new JObject { ["itemId"] = value.ItemId, ["type"] = value.Type.TypeId, ["subtype"] = value.Type.SubtypeId, ["amount"] = (double)value.Amount })); inv["truncated"] = items.Count > 128; } inventories.Add(inv); } item["inventories"] = inventories; item["cargoInventory"] = block is IMyCargoContainer || block is IMyShipDrill || block is IMyShipConnector || block is IMyConveyorSorter; } if (includeScreens) { var screens = new JArray(); if (block is IMyTextSurfaceProvider provider) for (int i = 0; i < Math.Min(provider.SurfaceCount, 16); i++) screens.Add(Surface(provider.GetSurface(i), i)); else if (block is IMyTextSurface surface) screens.Add(Surface(surface, 0)); if (screens.Count > 0) item["screens"] = screens; } } private static JObject Surface(IMyTextSurface surface, int index) { return new JObject { ["index"] = index, ["name"] = surface.Name, ["displayName"] = surface.DisplayName, ["contentType"] = surface.ContentType.ToString(), ["text"] = Clip(surface.GetText(), 8192), ["script"] = surface.Script, ["surfaceSize"] = new JObject { ["x"] = surface.SurfaceSize.X, ["y"] = surface.SurfaceSize.Y } }; } } }