using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; using Sandbox.Definitions; using Sandbox.ModAPI.Ingame; using VRage; using VRage.Game; using VRage.Game.ModAPI.Ingame; using Terminal = Sandbox.ModAPI.Ingame.IMyTerminalBlock; namespace XFE.SeAgent.Plugin.Game { public sealed partial class GameDebugApi { private JObject InventoryRoute(JObject args) { InventoryParameters(args, "sourceId", "targetId", "sourceInventoryIndex", "targetInventoryIndex", "itemType"); var sourceBlock = Block(InventoryEntityId(args, "sourceId")); var targetBlock = Block(InventoryEntityId(args, "targetId")); int sourceIndex = InventoryIndex(args, "sourceInventoryIndex"); int targetIndex = InventoryIndex(args, "targetInventoryIndex"); var source = InventoryAt(sourceBlock, sourceIndex); var target = InventoryAt(targetBlock, targetIndex); var definition = InventoryDefinition(args["itemType"] ?? new JValue("MyObjectBuilder_Ore/Stone"), false); var itemType = MyItemType.Parse(definition.ToString()); return new JObject { ["source"] = InventorySummary(sourceBlock, source, sourceIndex), ["target"] = InventorySummary(targetBlock, target, targetIndex), ["itemType"] = definition.ToString(), ["amountTested"] = 1, ["isConnectedTo"] = source.IsConnectedTo(target), ["canTransferItemTo"] = source.CanTransferItemTo(target, itemType), ["targetCanAddOne"] = target.CanItemsBeAdded((MyFixedPoint)1, itemType) }; } private JObject SetSorterFilters(JObject args) { InventoryParameters(args, "entityId", "mode", "items"); long entityId = InventoryEntityId(args, "entityId"); string modeText = Text(args, "mode"); MyConveyorSorterMode mode; if (modeText == "Whitelist") mode = MyConveyorSorterMode.Whitelist; else if (modeText == "Blacklist") mode = MyConveyorSorterMode.Blacklist; else throw new ArgumentException("mode must be Whitelist or Blacklist."); var items = args["items"] as JArray; if (items == null || items.Count > 128) throw new ArgumentException("items must be an array of at most 128 filters; an empty array explicitly clears the filters."); var filters = new List(); var keys = new HashSet(StringComparer.Ordinal); foreach (var token in items) { var item = token as JObject ?? throw new ArgumentException("Each filter must be an object with itemId and optional allSubTypes."); InventoryParameters(item, "itemId", "allSubTypes"); bool allSubTypes = false; if (item["allSubTypes"] != null) { if (item["allSubTypes"].Type != JTokenType.Boolean) throw new ArgumentException("allSubTypes must be a JSON boolean."); allSubTypes = (bool)item["allSubTypes"]; } var definition = InventoryDefinition(item["itemId"], allSubTypes); var filter = new MyInventoryItemFilter(definition, allSubTypes); if (!keys.Add(FilterKey(filter))) throw new ArgumentException("Duplicate sorter filter."); filters.Add(filter); } var sorter = Block(entityId) as IMyConveyorSorter ?? throw new ArgumentException("Entity is not a conveyor sorter."); var previous = DescribeSorter(sorter); // All inputs have been validated before this sole hardware mutation. SetFilter // replaces only mode/list; DrainAll, Enabled, names and other sorters are untouched. sorter.SetFilter(mode, filters); var actual = new List(); sorter.GetFilterList(actual); bool matches = sorter.Mode == mode && actual.Count == filters.Count && actual.All(value => keys.Contains(FilterKey(value))); _log("Replace sorter filters on " + Sid(entityId) + ": " + modeText + ", " + filters.Count + " entries; readback matches=" + matches); return new JObject { ["entityId"] = Sid(entityId), ["submitted"] = true, ["readbackMatchesRequest"] = matches, ["previous"] = previous, ["sorter"] = DescribeSorter(sorter, actual) }; } private static JObject DescribeSorter(IMyConveyorSorter sorter, List filters = null) { if (filters == null) { filters = new List(); sorter.GetFilterList(filters); } return new JObject { ["mode"] = sorter.Mode.ToString(), ["drainAll"] = sorter.DrainAll, ["items"] = new JArray(filters.Take(128).Select(value => new JObject { ["itemId"] = value.ItemId.ToString(), ["allSubTypes"] = value.AllSubTypes })), ["total"] = filters.Count, ["truncated"] = filters.Count > 128 }; } private static string FilterKey(MyInventoryItemFilter filter) { return filter.AllSubTypes ? filter.ItemId.TypeId.ToString() + "/*" : filter.ItemId.ToString(); } private static MyDefinitionId InventoryDefinition(JToken token, bool allSubTypes) { if (token == null || token.Type != JTokenType.String) throw new ArgumentException("itemId/itemType must be a string."); string text = (string)token; if (text.Length == 0 || text.Length > 256 || text.Any(char.IsControl) || text != text.Trim()) throw new ArgumentException("itemId/itemType must contain 1..256 characters without surrounding whitespace or control characters."); int slash = text.IndexOf('/'); if (slash < 0 && allSubTypes) { text += "/"; slash = text.Length - 1; } if (slash <= 0 || text.IndexOf('/', slash + 1) >= 0 || !text.StartsWith("MyObjectBuilder_", StringComparison.Ordinal)) throw new ArgumentException("Use MyObjectBuilder_Type/Subtype; an allSubTypes filter may use MyObjectBuilder_Type alone."); string subtype = text.Substring(slash + 1); if (allSubTypes ? subtype.Length > 0 && subtype != "(null)" : subtype.Length == 0 || subtype == "(null)") throw new ArgumentException("allSubTypes requires an empty subtype; a specific item requires a nonempty subtype."); if (text.Substring(0, slash).Any(c => !(char.IsLetterOrDigit(c) || c == '_')) || subtype != subtype.Trim()) throw new ArgumentException("Invalid item definition syntax."); MyDefinitionId definition; if (!MyDefinitionId.TryParse(text, out definition) || !typeof(MyObjectBuilder_PhysicalObject).IsAssignableFrom((Type)definition.TypeId)) throw new ArgumentException("Item type is not a registered physical inventory item type."); if (!allSubTypes && MyDefinitionManager.Static.TryGetPhysicalItemDefinition(definition) == null) throw new ArgumentException("Item definition does not exist in the current world."); return definition; } private static long InventoryEntityId(JObject args, string key) { var token = args[key]; if (token == null || (token.Type != JTokenType.String && token.Type != JTokenType.Integer)) throw new ArgumentException(key + " must be a nonzero decimal Int64 string or integer."); string text = (string)token; int firstDigit = text.StartsWith("-", StringComparison.Ordinal) ? 1 : 0; if (text.Length <= firstDigit || text.Length > 20 || text.Skip(firstDigit).Any(c => c < '0' || c > '9')) throw new ArgumentException(key + " must be a nonzero decimal Int64 string or integer."); long id; if (!long.TryParse(text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out id) || id == 0) throw new ArgumentException(key + " must be a nonzero decimal Int64 string or integer."); return id; } private static int InventoryIndex(JObject args, string key) { var token = args[key]; if (token == null) return 0; int index; if (token.Type != JTokenType.Integer || !int.TryParse((string)token, NumberStyles.None, CultureInfo.InvariantCulture, out index) || index < 0 || index > 15) throw new ArgumentException(key + " must be a JSON integer from 0 to 15."); return index; } private static IMyInventory InventoryAt(Terminal block, int index) { if (!block.HasInventory || index >= block.InventoryCount) throw new ArgumentException("Inventory index does not exist on block " + Sid(block.EntityId) + "."); return block.GetInventory(index) ?? throw new ArgumentException("Block inventory is unavailable."); } private static JObject InventorySummary(Terminal block, IMyInventory inventory, int index) { return new JObject { ["entityId"] = Sid(block.EntityId), ["gridId"] = Sid(block.CubeGrid.EntityId), ["index"] = index, ["massKg"] = (double)inventory.CurrentMass, ["volumeM3"] = (double)inventory.CurrentVolume, ["maxVolumeM3"] = (double)inventory.MaxVolume, ["freeVolumeM3"] = Math.Max(0, (double)inventory.MaxVolume - (double)inventory.CurrentVolume), ["isFull"] = inventory.IsFull, ["canPutItems"] = inventory.CanPutItems, ["itemCount"] = inventory.ItemCount }; } private static void InventoryParameters(JObject args, params string[] names) { foreach (var property in args.Properties()) if (!names.Contains(property.Name, StringComparer.Ordinal)) throw new ArgumentException("Unknown parameter: " + property.Name); } } }