Skip to content

Instantly share code, notes, and snippets.

@SuperFlue
Last active June 22, 2024 18:40
Show Gist options
  • Save SuperFlue/d596c0eaaa40867bfd34c292458e2281 to your computer and use it in GitHub Desktop.
Save SuperFlue/d596c0eaaa40867bfd34c292458e2281 to your computer and use it in GitHub Desktop.
Inventory sorting script for Empyrion Galactic Survival
// Automatic sorting script for Empyrion by SuperFlue
// This script will automaticall sort items to various containers based on tags
// Needs https://github.com/GitHub-TC/EmpyrionScripting to work
// Place in "Empyrion - Galactic Survival\Saves\Games\<SaveName>\Mods\EmpyrionScripting\Scripts
// Updates will generally be available at https://gist.github.com/SuperFlue/d596c0eaaa40867bfd34c292458e2281
using Eleon.Modding;
using EmpyrionScripting.Interface;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
internal class ModMain
{
// Adjust these to your preferences
// The prefix is used to include the storage in the sorting system
static string validPrefix = "Sort-";
// This is the name of the input storage where things will be sorted out off
static string inputStorageName = "Input";
// This is the box where anything that did not have a more specific sorting ends up, or if other storages are full
static string unsortedOutput = "Dumpbox";
// These lists allows you to define some static tag overrides for items
// There is one for item names, and one for item-IDs
// Moving is based on "first hit wins" so items are moved based on which rule they hit first
// The name list is evaluated before the ID list
// There is also an override that can be used for specific entities (ie. different bases).
// Create a LCD with the name "SuperSort-config" to configure that
// The overrides defined on the LCD wins over any other configuration
static List<(string name, string storage)> overrideTargetsName = new List<(string name, string storage)>
{
("PromethiumOre", "Crafting" ),
("PentaxidOre", "Crafting" ),
};
static List<(int id, string storage)> overrideTargetsID = new List<(int id, string storage)>
{
(4159, "Fuel"),
};
// This is the generic mappings of itemgroups to tags, these are based on the item groups that are defined in the Configuration.json for the scripting mod.
// We duplicate some lines her allowing for more specific storage to be used before less specific ex. Small Devices goes into DevicesS before the general Devices container
static List<(string type, string storage)> genericTargets = new List<(string type, string storage)>
{
("OreFurnace", "Furnace"),
("Ore", "Ore"),
("Ingot", "Crafting"),
("Medic", "Medical"),
("Food", "Food"),
("Sprout", "Plant"),
("Tools", "Tools"),
("Fuel", "Fuel" ),
("ArmorMod", "Boosts"),
("BlockL", "BlocksL"),
("BlockS", "BlocksS"),
("BlockL", "Blocks"),
("BlockS", "Blocks"),
("DeviceL", "DevicesL"),
("DeviceS", "DevicesS"),
("DeviceL", "Devices"),
("DeviceS", "Devices"),
("WeaponPlayer", "WeaponsP"),
("WeaponPlayerEpic", "WeaponsP"),
("WeaponPlayerUpgrades", "WeaponsP"),
("WeaponHV", "WeaponsS"),
("WeaponSV", "WeaponsS"),
("WeaponBA", "WeaponsL"),
("WeaponCV", "WeaponsL"),
("WeaponPlayer", "Weapons"),
("WeaponPlayerEpic", "Weapons"),
("WeaponPlayerUpgrades", "Weapons"),
("WeaponHV", "Weapons"),
("WeaponSV", "Weapons"),
("WeaponBA", "Weapons"),
("WeaponCV", "Weapons"),
("AmmoPlayer", "AmmoP"),
("AmmoHV", "AmmoS"),
("AmmoSV", "AmmoS"),
("AmmoCV", "AmmoL"),
("AmmoBA", "AmmoL"),
("AmmoAllEnergy", "Ammo"),
("AmmoAllProjectile", "Ammo"),
("AmmoAllRocket", "Ammo"),
("Armor", "Armor"),
("Deconstruct", "Deconstruct"),
("Deco", "Deco"),
("DataPads", "Datapads"),
("Components", "Crafting"),
("EdenComponents", "Crafting"),
("Ingredient", "Crafting"),
("IngredientBasic", "Crafting"),
("IngredientExtra", "Crafting"),
("IngredientExtraMod", "Crafting")
};
// The name of the main screen, this is the name of the LCD that activates the script on the entity
static string mainScreenName = "SuperSort-main";
// This is the name of the config screens where you define custom targets for items
// Needs to have a * wildcard at the end of the name to be able to pic up multiple LCDs
static string configScreensName = "SuperSort-config*";
static string debugScreenName = "SuperSort-debug";
// There should no be a need to modify stuff below here generally
static StringBuilder lcdInfoStringBuilder = new StringBuilder();
static StringBuilder lcdConfigStringBuilder = new StringBuilder();
static List<(int id, string storage)> customOverrideTargets = new List<(int id, string storage)>();
static bool doneFirstRun = false;
static string lcdInfo;
static List<IItemsData> dumpStorageItems = new List<IItemsData>();
static List<string> allValidDeviceNames = new List<string>();
// Stopwatch to keep track of runtime
static Stopwatch stopWatch = new Stopwatch();
static string helpString = @"<size=50%># Use this screen to set up custom sorting
# Use the format 'ITEMID = CUSTOMTAG'
# For example '4159 = BioFuel' to move Bio Fuel to containers tagged with 'BioFuel'
# Lines starting with # will be ignored by the script
# This help text can be safely removed, it will be re-added if LCD is empty
# You can use multiple LCDs if needed";
public static void Main(IScriptModData rootObject)
{
// Make sure to exit out early if we dont need to do anything
if (!(rootObject is IScriptSaveGameRootData root)) return;
if (root.E.Faction.Id == 0) return;
// Make sure there is an LCD that begins with the SuperSort keyword before we run the rest of the script
var superSortMainLCD = root.CsRoot.GetDevices<ILcd>(root.E.S, mainScreenName);
var superSortDebugScreen = root.CsRoot.GetDevices<ILcd>(root.E.S, debugScreenName);
if (superSortMainLCD.Length == 0)
{
return;
}
stopWatch.Restart();
if (!doneFirstRun)
{
WriteToDebug(superSortDebugScreen, "--Init " + DateTime.Now.ToString("s"), true);
// Lowercase all names
validPrefix = validPrefix.ToLower();
inputStorageName = inputStorageName.ToLower();
unsortedOutput = unsortedOutput.ToLower();
// Need to use temp variables and Select to lowercase the storage names
var temp1 = overrideTargetsID.Select(x => (x.id, x.storage = x.storage.ToLower())).ToList();
overrideTargetsID.Clear();
overrideTargetsID.AddRange(temp1);
var temp2 = genericTargets.Select(x => (x.type, x.storage = x.storage.ToLower())).ToList();
genericTargets.Clear();
genericTargets.AddRange(temp2);
var temp3 = overrideTargetsName.Select(x => (x.name, x.storage = x.storage.ToLower())).ToList();
overrideTargetsName.Clear();
overrideTargetsName.AddRange(temp3);
}
allValidDeviceNames.Clear();
allValidDeviceNames.AddRange(root.E.S.AllCustomDeviceNames.Select(s => s.ToLower()).Where(s => s.StartsWith(validPrefix)).ToList());
// Get the items to move
dumpStorageItems.Clear();
foreach (var deviceName in allValidDeviceNames.Where(s => s.Contains(inputStorageName)))
{
dumpStorageItems.AddRange(root.CsRoot.Items(root.E.S, deviceName));
}
// Get the LCDs with the config keyword and proccess these if they exists
var superSortConfigDevices = root.CsRoot.Devices(root.E.S, configScreensName);
customOverrideTargets.Clear();
if (superSortConfigDevices.Length != 0)
{
lcdConfigStringBuilder.Clear();
var superSortConfigLCD = root.CsRoot.GetDevices<ILcd>(superSortConfigDevices);
foreach (var confLCD in superSortConfigLCD)
{
var currentstring = confLCD.GetText();
if (currentstring.Length == 0)
{
confLCD.SetText(helpString);
}
else if (currentstring == "Enter text here...")
{
confLCD.SetText(helpString);
}
else
{
_ = lcdConfigStringBuilder.AppendLine(currentstring);
}
}
_ = lcdConfigStringBuilder.Replace("<size=50%>", "");
var lcdStrings = SplitToLines(lcdConfigStringBuilder.ToString()).Where(s => !s.StartsWith("#") && !string.IsNullOrWhiteSpace(s)).ToArray();
foreach (var configLine in lcdStrings)
{
// Simple way to just make there are somewhat resonable input in the override dictonary
try
{
var splitline = configLine.Split('=');
if (!string.IsNullOrWhiteSpace(splitline[1]) && int.TryParse(splitline[0], out int itemid))
{
if (root.ConfigEcfAccess.IdBlockMapping.ContainsKey(itemid))
{
customOverrideTargets.Add((itemid, splitline[1].Trim().ToLower()));
}
}
}
catch (Exception ex)
{
}
}
}
//if there is no items to move we exit
if (dumpStorageItems.Count <= 0)
{
stopWatch.Stop();
return;
}
foreach (var item in dumpStorageItems)
{
bool moved = false;
// Move items on the customOverrideTargets list
if (!moved)
{
foreach (var overrideTargetID in customOverrideTargets)
{
if (overrideTargetID.id == item.Id)
{
moved = TryMoveItem(root, allValidDeviceNames, item, moved, overrideTargetID.storage);
if (moved) { break; }
}
}
}
// Move items on the overrideTargetsName list
if (!moved)
{
foreach (var overrideTargetName in overrideTargetsName)
{
if (overrideTargetName.name == item.Name)
{
moved = TryMoveItem(root, allValidDeviceNames, item, moved, overrideTargetName.storage);
if (moved) { break; }
}
}
}
// Move items on the overrideTargetsID list
if (!moved)
{
foreach (var overrideTargetID in overrideTargetsID)
{
if (overrideTargetID.id == item.Id)
{
moved = TryMoveItem(root, allValidDeviceNames, item, moved, overrideTargetID.storage);
if (moved) { break; }
}
}
}
// Move items based on the generic lists
if (!moved)
{
foreach (var genericTarget in genericTargets)
{
if (root.CsRoot.Root.PlainIds[genericTarget.type].Contains(item.Key))
{
moved = TryMoveItem(root, allValidDeviceNames, item, moved, genericTarget.storage);
if (moved) { break; }
}
}
}
// Catch anything uncategorized/not moved
if (!moved)
{
moved = TryMoveItem(root, allValidDeviceNames, item, moved, unsortedOutput);
}
}
// Create our output data
_ = lcdInfoStringBuilder.AppendLine("--- Finished sorting at " + DateTime.Now.ToString("s") + " runtime: " + stopWatch.Elapsed.TotalSeconds.ToString("0.0000") + "s ---");
_ = lcdInfoStringBuilder.Append(superSortMainLCD.First().GetText().Trim());
_ = lcdInfoStringBuilder.Replace("<size=50%>", "");
_ = lcdInfoStringBuilder.Insert(0, "<size=50%>");
stopWatch.Stop();
lcdInfo = lcdInfoStringBuilder.ToString();
lcdInfoStringBuilder.Clear();
// Print to all main LCDs
foreach (ILcd lcd in superSortMainLCD)
{
lcd.SetText(lcdInfo);
}
if (!doneFirstRun)
{
doneFirstRun = true;
}
}
private static bool TryMoveItem(IScriptSaveGameRootData root, IEnumerable<string> allValidDeviceNames, IItemsData item, bool moved, string storage, ILcd[] debugScreens = null)
{
storage = storage.ToLower();
if (null != debugScreens)
{
WriteToDebug(debugScreens, "Trying to move " + item.Name + " to " + storage);
WriteToDebug(debugScreens, "Device list: " + string.Join(", ", allValidDeviceNames) + "\n");
WriteToDebug(debugScreens, "Valid storage: " + string.Join("\n", allValidDeviceNames.Where(s => s.Contains(storage))) + "\n" );
}
foreach (var deviceName in allValidDeviceNames.Where(s => s.Contains(storage)))
{
var moveResult = root.CsRoot.Move(item, root.E.S, deviceName);
if (null != debugScreens)
{
WriteToDebug(debugScreens, "Tried to move " + item.Name + " to " + deviceName + "\nMove result is " + moveResult.Count);
}
if (moveResult.Count != 0)
{
AppendItemMoveInfoString(lcdInfoStringBuilder, moveResult, item.Name);
moved = true;
break;
}
}
return moved;
}
//Debug screen helper
private static void WriteToDebug(ILcd[] debugLCDs, string debugline, bool clear = false)
{
string currentText = string.Empty;
foreach (ILcd lcd in debugLCDs)
{
currentText = lcd.GetText();
if (currentText == "clear" || clear)
{
lcd.SetText(debugline);
}
else
{
lcd.SetText(debugline + "\n" + currentText);
}
}
}
//Helper to split the string from an LCD
private static IEnumerable<string> SplitToLines(string input)
{
if (input == null)
{
yield break;
}
using (System.IO.StringReader reader = new System.IO.StringReader(input))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
// Helper for writing infostrings for moving and to make it consistent
private static void AppendItemMoveInfoString(StringBuilder lcdStringBuilder, IList<IItemMoveInfo> moveInfo, string ItemName = null)
{
string itemInfoTextTemplate = "Moved {2}x '" + ItemName + "' to '{1}' (ID: {0})";
foreach (var item in moveInfo)
{
_ = lcdStringBuilder.AppendLine(string.Format(itemInfoTextTemplate, item.Id, item.Destination, item.Count));
if (!string.IsNullOrWhiteSpace(item.Error))
{
_ = lcdStringBuilder.AppendLine($"Failed to move {item.Id}, error: {item.Error}");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment