Skip to content

Instantly share code, notes, and snippets.

@LexManos
Last active April 21, 2018 02:16
Show Gist options
  • Save LexManos/7c4746b17a39f38d9ab8a682bb031e9e to your computer and use it in GitHub Desktop.
Save LexManos/7c4746b17a39f38d9ab8a682bb031e9e to your computer and use it in GitHub Desktop.
SpaceEngineersSorting
//Basic Readme:
//Name your inventories with <ItemName!Priority#Count,ItemName2!Priority2#Count2>
//Name is the name of the item you want, leave it empty for all items, categories also work: ingot, ore, component, or whatever other categories Space Engineers has.
//Priority is optional, defaults to 0, higher priorities will get fed first.
//Count is optional, this will be the number of this item in the inventory for it to be 'satisfied'. And able to move the extra to lower priority inventories.
//I've included defaults for a few things, like refinearies, assemblers, o2 gens, gatling guns, etc..
//But you can always override them by manually specfying in the name.
//This is a major work in progress and i'll be tweaking it as I play. But it works for me... so have fun, no support/warranty/etc..
// Set this to true, and the debug log from each tick will be set to the programming block's Custom Data field.
private static readonly bool DEBUG = false;
// Frequency in 100 tick increments when to rescan the structure for inventories to manipulate.
private int RebuildFrequency { get; } = 100;
// Max number of intentories to process per update. To prevent 'to complex' issues.
private static readonly int MaxUpdates = 10;
//==========================================================================================================
private int Rebuild { get; set; }
private static Program Instance { get; set; }
private static Dictionary<string, string> Categories { get; } = new Dictionary<string, string>();
private Dictionary<string, List<InventoryWrapper>> InventoryPriorities { get; } =
new Dictionary<string, List<InventoryWrapper>>();
private List<InventoryWrapper> Inventories { get; } = new List<InventoryWrapper>();
private StringBuilder _debugBuffer = new StringBuilder();
private int _lastIndex = 0;
public Program()
{
Runtime.UpdateFrequency = UpdateFrequency.Update10;
Categories.Clear();
//AddCategory("Tools",
// "WelderItem", "HandDrillItem", "AngleGrinderItem",
// "AutomaticRifleItem");
//AddCategory("Ammo",
// "Missile200mm", "NATO_25x184mm", "NATO_5p56x45mm", "Explosives");
Instance = this;
}
private void AddCategory(String cat, params string[] names)
{
foreach (var name in names)
{
Categories.Add(name.ToLower(), cat.ToLower());
}
}
public void Main(string argument, UpdateType updateSource)
{
if (DEBUG)
{
_debugBuffer = new StringBuilder();
Echo("-------------------------------------------------------------------------------------------------------");
}
if (Rebuild-- == 0)
{
BuildInventories();
Rebuild = RebuildFrequency;
}
else
{
foreach (var inv in Inventories)
{
inv.ClearCache(); //Reset counts incase other systems moved stuff between ticks.
}
if (_lastIndex >= Inventories.Count)
{
_lastIndex = 0;
}
int processed = 0;
for (; _lastIndex < Inventories.Count; _lastIndex++)
{
if (processed++ == MaxUpdates)
{
break;
}
SortInventory(Inventories[_lastIndex]);
}
}
if (DEBUG)
{
Me.CustomData = _debugBuffer.ToString();
}
}
public void Echo(string line)
{
_debugBuffer?.Append(line).Append('\n');
}
private void BuildInventories()
{
var blocks = new List<IMyTerminalBlock>();
GridTerminalSystem.GetBlocksOfType<IMyEntity>(blocks);
Inventories.Clear();
_lastIndex = 0;
InventoryPriorities.Clear();
foreach (var block in blocks)
{
if (!block.HasInventory || block.InventoryCount <= 0)
{
continue;
}
var _default =
block is IMyRefinery ? DefaultRefinery :
block is IMyReactor ? DefaultReactor :
block is IMyAssembler ? DefaultAssembler :
block is IMyCargoContainer ? DefaultCargo :
block is IMyGasGenerator ? DefaultO2Gen :
block is IMyLargeGatlingTurret ? DefaultGatlingTurret :
DefaultSortArr;
var inv = new InventoryWrapper(block.GetInventory(0), block.CustomName, false, _default);
AddInventory(inv);
if (block is IMyProductionBlock)
{
AddInventory(new InventoryWrapper(((IMyProductionBlock) block).OutputInventory, inv.BlockName + " Output", true, DefaultSortArr));
}
}
foreach (var ent in InventoryPriorities)
{
//Sort it based on priority, higher numbers at top, and anything with a count before non-counted
ent.Value.Sort(Comparer<InventoryWrapper>.Create((o1, o2) =>
{
var p1 = o1.Predicates.GetValueOrDefault(ent.Key, DefaultSort);
var p2 = o2.Predicates.GetValueOrDefault(ent.Key, DefaultSort);
var ret = p2.Priority - p1.Priority;
if (ret == 0)
{
ret = p1.Count == p2.Count ? 0 : p1.Count == 0 ? 1 : p2.Count == 0 ? -1 : 0;
}
if (ret == 0)
{
ret = String.CompareOrdinal(o1.BlockName, o2.BlockName);
}
return ret;
}));
if (DEBUG)
{
Echo(ent.Key + ":");
foreach (var w in ent.Value)
{
Echo(" " + w.BlockName + " " + w.Predicates.GetValueOrDefault(ent.Key));
}
}
}
}
private void AddInventory(InventoryWrapper wrapper)
{
Inventories.Add(wrapper);
if (wrapper.ExtractOnly)
{
return;
}
foreach (var pred in wrapper.Predicates)
{
var lst = InventoryPriorities.GetValueOrDefault(pred.Key);
if (lst == null)
{
lst = new List<InventoryWrapper>();
InventoryPriorities[pred.Key] = lst;
}
lst.Add(wrapper);
}
}
//TODO: Sort from lower priority slots, and less strict slots to the more strict ones.
// Iron Ingot x100 in <Ingot> with another Box <Iron Ingot> with space.
// Iron Ingot x100 in <Ingot> with another Box <Ingot!1> with space.
private void SortInventory(InventoryWrapper inv)
{
var items = inv.Inventory.GetItems();
for (var x = items.Count - 1; x >= 0; x--)
{
var item = items[x];
var extra = inv.GetExtra(item);
if (extra <= 0)
{
continue;
}
if (DEBUG)
{
Echo(inv.BlockName + ": Item: " + GetName(item) + " Extra: " + extra);
}
var foundSelf = false;
for (var i = 0; i < 3; i++)
{
if (extra <= 0 || foundSelf)
{
break;
}
var key = i == 0 ? GetName(item) :
i == 1 ? GetCategory(item) :
DefaultSort.Desc;
var lst = InventoryPriorities.GetValueOrDefault(key);
if (lst == null)
{
continue;
}
if (DEBUG)
{
Echo(" " + key + ":");
}
var minPriority = int.MinValue;
foreach (var other in lst)
{
if (DEBUG)
{
Echo(" " + other.BlockName + ": " + other.Predicates[key].Priority);
}
if (other == inv)
{
minPriority = inv.Predicates[key].Priority; //If we were found in this list, then go no further then our sibling priorities.
foundSelf = true;
continue;
}
if (minPriority > other.Predicates[key].Priority)
{
break;
}
if (!inv.Inventory.IsConnectedTo(other.Inventory))
{
continue;
}
var needed = other.GetNeeded(key);
if (needed > 0)
{
if (DEBUG)
{
Echo(" Moving: " + key + " " + inv.BlockName + " -> " + other.BlockName + " " + needed);
}
if (inv.Inventory.TransferItemTo(other.Inventory, x, null, true,
extra > needed ? needed : extra))
{
inv.ClearCache(item);
other.ClearCache(item);
var oldLen = items.Count;
items = inv.Inventory.GetItems();
if (oldLen != items.Count) //We removed the stack so kick out.
{
extra = 0;
break;
}
item = items[x];
extra = inv.GetExtra(item);
if (DEBUG)
{
Echo(" Extra: " + extra);
}
}
}
if (extra <= 0)
{
break;
}
}
}
}
}
private static readonly Dictionary<string, string> NameCache = new Dictionary<string, string>();
private static string GetName(IMyInventoryItem item)
{
var content = item.Content;
var key = content.TypeId + " " + content.SubtypeName;
var ret = NameCache.GetValueOrDefault(key);
if (ret == null)
{
var category = GetCategory(item);
ret = item.Content.SubtypeName;
if (category == "Ore" || category == "Ingot")
{
ret += " " + category;
}
//These Items don't quite match the in-game names so lets fix them.
if (ret == "Stone Ingot")
{
ret = "Gravel";
}
else if (ret == "Silicon Ingot")
{
ret = "Silicon Wafer";
}
else if (ret == "Magnesium Ingot")
{
ret = "Magnesium Powder";
}
else if (ret == "Scrap Ore")
{
ret = "Scrap Metal";
}
else if (ret == "Ice Ore")
{
ret = "Ice";
}
NameCache[key] = ret.ToLower();
}
return ret;
}
private static readonly string BuilderString = "MyObjectBuilder_";
private static readonly Dictionary<string, string> CatCache = new Dictionary<string, string>();
private static string GetCategory(IMyInventoryItem item)
{
var content = item.Content;
var key = content.TypeId + " " + content.SubtypeName;
var ret = CatCache.GetValueOrDefault(key);
if (ret == null)
{
var type = content.TypeId.ToString();
if (type.StartsWith(BuilderString))
{
ret = type.Substring(BuilderString.Length);
}
else if (Categories.ContainsKey(content.SubtypeName))
{
ret = Categories[item.Content.SubtypeName];
}
else
{
if (DEBUG)
{
Instance.Echo("Unknown Category: " + item.Content.SubtypeName + " " + content.TypeId);
}
ret = type;
}
CatCache[key] = ret.ToLower();
}
return ret;
}
private class InventoryWrapper
{
public readonly Dictionary<string, ItemPredicate> Predicates = new Dictionary<string, ItemPredicate>();
public readonly IMyInventory Inventory;
public readonly bool ExtractOnly;
public readonly string BlockName;
private readonly Dictionary<string, VRage.MyFixedPoint> _counts = new Dictionary<string, VRage.MyFixedPoint>();
public InventoryWrapper(IMyInventory inv, String name, bool extractOnly = false, ItemPredicate[] defaults = null)
{
BlockName = name;
if (!extractOnly)
{
var start = name.IndexOf('<');
var end = start == -1 ? -1 : name.IndexOf('>', start + 1);
if (start != -1 && end != -1 && start < end)
{
foreach (var part in name.Substring(start + 1, end - start - 1).Split(','))
{
var pred = new ItemPredicate(part.Trim());
Predicates[pred.Desc] = pred;
}
}
}
if (Predicates.Count == 0)
{
if (defaults != null)
{
foreach (var def in defaults)
{
if (def != null)
{
Predicates[def.Desc] = def;
}
}
}
}
Inventory = inv;
ExtractOnly = extractOnly;
}
public VRage.MyFixedPoint GetExtra(IMyInventoryItem item)
{
if (Predicates.Count == 0)
{
return item.Amount;
}
var pred = GetPredicate(item);
if (pred == null)
{
return item.Amount;
}
return pred.Count == 0 ? item.Amount : GetCount(pred.Desc) - pred.Count;
}
private ItemPredicate GetPredicate(IMyInventoryItem item)
{
var pred = Predicates.GetValueOrDefault(GetName(item));
return pred == null ? Predicates.GetValueOrDefault(GetCategory(item)) : pred;
}
public void ClearCache(IMyInventoryItem item = null)
{
if (item == null)
{
_counts.Clear();
}
else
{
_counts.Remove(GetName(item));
_counts.Remove(GetCategory(item));
}
}
private VRage.MyFixedPoint GetCount(string key)
{
if (_counts.ContainsKey(key))
{
return _counts[key];
}
VRage.MyFixedPoint count = 0;
foreach (var item in Inventory.GetItems())
{
var pred = GetPredicate(item);
if (pred != null && pred.Desc == key)
{
count += item.Amount;
}
}
_counts[key] = count;
return count;
}
public VRage.MyFixedPoint GetNeeded(string key)
{
var pred = Predicates.GetValueOrDefault(key);
return pred == null ? 0 : pred.Count == 0 ? VRage.MyFixedPoint.MaxValue : pred.Count - GetCount(key);
}
}
private static readonly ItemPredicate DefaultSort = new ItemPredicate("Default!-1");
private static readonly ItemPredicate[] DefaultSortArr = {DefaultSort};
private static readonly ItemPredicate[] DefaultCargo = {new ItemPredicate("!0")};
private static readonly ItemPredicate[] DefaultAssembler = //These are all the max values for vanilla recipes.
{
new ItemPredicate("Silicon Wafer!1#12"),
new ItemPredicate("Iron Ingot!1#200"),
new ItemPredicate("Nickel Ingot!1#24"),
new ItemPredicate("Magnesium Powder!1#1"),
new ItemPredicate("Gold Ingot!1#4"),
new ItemPredicate("Cobalt Ingot!1#74"),
new ItemPredicate("Silver Ingot!1#7"),
new ItemPredicate("Uranium Ingot!1#0.05"),
new ItemPredicate("Platinum Ingot!1#1"),
new ItemPredicate("Gravel!1#7")
};
private static readonly ItemPredicate[] DefaultRefinery = {new ItemPredicate("Ore#100")};
private static readonly ItemPredicate[] DefaultReactor = {new ItemPredicate("Uranium Ingot!1#1")};
private static readonly ItemPredicate[] DefaultO2Gen = {new ItemPredicate("Ice!1#100")};
private static readonly ItemPredicate[] DefaultGatlingTurret = {new ItemPredicate("NATO_25x184mm!1#25")};
private class ItemPredicate
{
public readonly int Priority;
public readonly VRage.MyFixedPoint Count;
public readonly string Desc;
public ItemPredicate(string desc)
{
int pi = desc.IndexOf('!');
int ci = desc.IndexOf('#');
if (pi != -1)
{
if (ci != -1 && ci > pi)
{
Count = (VRage.MyFixedPoint)double.Parse(desc.Substring(ci + 1));
desc = desc.Substring(0, ci);
ci = -1;
}
Priority = int.Parse(desc.Substring(pi + 1));
desc = desc.Substring(0, pi);
}
if (ci != -1)
{
Count = (VRage.MyFixedPoint)double.Parse(desc.Substring(ci + 1));
desc = desc.Substring(0, ci);
}
desc = desc.Trim();
Desc = (desc.Length == 0 && DefaultSort != null ? DefaultSort.Desc : desc).ToLower();
}
public override string ToString()
{
return Desc + "!" + Priority + '#' + Count;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment