Last active
June 22, 2024 18:40
-
-
Save SuperFlue/d596c0eaaa40867bfd34c292458e2281 to your computer and use it in GitHub Desktop.
Inventory sorting script for Empyrion Galactic Survival
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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