Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PrefabDependencyTree v1.3.0
plugins\PrefabDependencyTree.dll
Decompiled 2 years agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using DotNetGraph.Attributes; using DotNetGraph.Compilation; using DotNetGraph.Core; using DotNetGraph.Extensions; using JetBrains.Annotations; using Jotunn.Entities; using Jotunn.Managers; using MonoMod.Utils; using PrefabDependencyTree.Data; using PrefabDependencyTree.Data.Drops; using PrefabDependencyTree.Data.Drops.Generic; using PrefabDependencyTree.Data.Filters; using PrefabDependencyTree.Model; using PrefabDependencyTree.Util; using UnityEngine; [assembly: AssemblyFileVersion("1.2.5")] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PrefabDependencyTree")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("FixItFelix")] [assembly: AssemblyProduct("PrefabDependencyTree")] [assembly: AssemblyCopyright("Copyright 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: CompilationRelaxations(8)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.5.0")] [module: UnverifiableCode] namespace PrefabDependencyTree { [BepInPlugin("FixItFelix.PrefabDependencyTree", "PrefabDependencyTree", "1.2.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] internal class PrefabDependencyTreePlugin : BaseUnityPlugin { public const string PluginAuthor = "FixItFelix"; public const string PluginGUID = "FixItFelix.PrefabDependencyTree"; public const string PluginName = "PrefabDependencyTree"; public const string PluginVersion = "1.2.5"; private void Awake() { PrefabManager.OnPrefabsRegistered += DataHarvester.Initialize; CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ConsoleController()); } } } namespace PrefabDependencyTree.Util { public class ConsoleController : ConsoleCommand { private const string prefabDependencyTreeCommand = "prefab_dependency_tree"; private const string printOption = "print_tree"; private const string printIncludeFilteredOption = "print_include_filtered_tree"; private const string printExcludeFilteredOption = "print_exclude_filtered_tree"; private const string debugLogAll = "debug_log_all"; private const string debugLogItemsCategories = "debug_log_items_categories"; private static readonly List<string> ItemTypeEnums = Enum.GetNames(typeof(ItemType)).ToList(); private static readonly List<ItemType> WeaponsAndArmorEnums = new List<ItemType> { (ItemType)9, (ItemType)4, (ItemType)7, (ItemType)10, (ItemType)12, (ItemType)6, (ItemType)11, (ItemType)5, (ItemType)17, (ItemType)19, (ItemType)15, (ItemType)18, (ItemType)20, (ItemType)23, (ItemType)3, (ItemType)14, (ItemType)22 }; private static readonly List<string> WeaponsAndArmorTypes = WeaponsAndArmorEnums.Select((ItemType enumEntry) => ((object)(ItemType)(ref enumEntry)).ToString()).ToList(); private const string WeaponsAndArmorTypesName = "WeaponsAndArmorTypes"; public override string Name => "prefab_dependency_tree"; public override string Help => "Prefab Dependency Tree Console Commands"; private static List<string> ReplaceTypes(List<string> input) { if (!input.Contains("WeaponsAndArmorTypes")) { return input; } input.Remove("WeaponsAndArmorTypes"); input.AddRange(WeaponsAndArmorTypes); return input; } public override void Run(string[] args) { Logger.LogInfo("called '" + ((ConsoleCommand)this).Name + "' with args '" + string.Join(" ", args) + "'"); if (args.Length != 0) { switch (args[0]) { case "print_tree": WriteGraphOutput(new UnfilteredData()); break; case "print_include_filtered_tree": if (args.Length > 1) { WriteGraphOutput(new IncludeFilterTypes(ReplaceTypes(args[1].Split(new char[1] { ',' }).ToList()))); } else { Logger.LogWarning("you did not provide item types to filter for, see usage"); LogUsage(); } break; case "print_exclude_filtered_tree": if (args.Length > 1) { WriteGraphOutput(new ExcludeFilterTypes(ReplaceTypes(args[1].Split(new char[1] { ',' }).ToList()))); } else { Logger.LogWarning("you did not provide item types to filter for, see usage"); LogUsage(); } break; case "debug_log_items_categories": { List<string> values = DataHarvester.LogAllItemsToCategorizedYaml(); string text2 = Path.Combine(Paths.ConfigPath, "FixItFelix.PrefabDependencyTree.all.items.categories.yaml"); File.WriteAllText(text2, string.Join("\n", values)); Logger.LogInfo("wrote file '" + text2 + "'"); break; } case "debug_log_all": { List<string> list = DataHarvester.LogAllToString(); list.ForEach(Logger.LogWarning); string text = Path.Combine(Paths.ConfigPath, "FixItFelix.PrefabDependencyTree.all.txt"); File.WriteAllText(text, string.Join("\n", list)); Logger.LogInfo("wrote file '" + text + "'"); break; } default: Logger.LogWarning("this option '" + args[0] + "' is not supported, see usage"); LogUsage(); break; } } else { LogUsage(); } } private static async void WriteGraphOutput(FilteredData filteredData) { using StringWriter writer = new StringWriter(); CompilationContext context = new CompilationContext(writer, new CompilationOptions()); filteredData.LogOverview(); await filteredData.CreateGraph().CompileAsync(context); string text = Path.Combine(Paths.ConfigPath, "FixItFelix.PrefabDependencyTree.graph.dot"); File.WriteAllText(text, writer.GetStringBuilder().ToString()); Logger.LogInfo("wrote graph file '" + text + "'"); RunGraphVizConverter(text); } private static void RunGraphVizConverter(string filePath) { string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Graphviz\\bin\\gv2gml.exe"); if (!File.Exists(text)) { return; } Logger.LogInfo("GraphViz installation binary detected at '" + text + "'"); try { string text2 = filePath.Replace(".dot", ".gml"); Process.Start(text, "\"" + filePath + "\" -o \"" + text2 + "\"")?.WaitForExit(100000); if (File.Exists(text2)) { Logger.LogInfo("converted to GraphViz file to '" + text2 + "'"); } else { Logger.LogWarning("converted file was not found as expected"); } } catch (Exception ex) { Logger.LogWarning("error converting .dot file using GraphViz: " + ex.Message); } } private static void LogUsage() { Logger.LogInfo(" - prefab_dependency_tree usage - "); Logger.LogInfo("command option:"); Logger.LogInfo(" print_tree -> will print the whole tree analyzed from game data"); Logger.LogInfo(" debug_log_all -> will log warn all prefabs (this will be huge log output!)"); Logger.LogInfo(" debug_log_items_categories -> writes all item prefabs by category into a yaml format file"); Logger.LogInfo(" print_include_filtered_tree Material,Consumable -> print tree with items of the provided item types included (complete tree with link to any included items)"); Logger.LogInfo(" print_exclude_filtered_tree Ammo,OneHandedWeapon -> print tree with items of the provided item types excluded (remove the provided types from tree)"); Logger.LogInfo(" all item types: " + string.Join(", ", ItemTypeEnums) + ", WeaponsAndArmorTypes"); } public override List<string> CommandOptionList() { return new List<string> { "print_tree", "print_include_filtered_tree", "print_exclude_filtered_tree", "debug_log_all", "debug_log_items_categories" }; } } public static class Logger { private static readonly ManualLogSource LoggerInstance = Logger.CreateLogSource("PrefabDependencyTree"); public static void LogDebug(string text) { LoggerInstance.LogDebug((object)text); } public static void LogInfo(string text) { LoggerInstance.LogInfo((object)text); } public static void LogWarning(string text) { LoggerInstance.LogWarning((object)text); } public static void LogError(string text) { LoggerInstance.LogError((object)text); } } } namespace PrefabDependencyTree.Model { public class BaseCrafting { public readonly string Name; public readonly Dictionary<string, GraphRecipe> Recipes = new Dictionary<string, GraphRecipe>(); protected BaseCrafting(string name) { Name = name; } protected BaseCrafting(string name, Dictionary<string, GraphRecipe> recipes) { Name = name; Recipes = recipes; } public bool ContainsItemTypes(List<string> itemTypes) { return Recipes.Any((KeyValuePair<string, GraphRecipe> recipe) => itemTypes.Contains(recipe.Value.CraftedItem.Item1.ItemType) || recipe.Value.RequiredItems.Any((KeyValuePair<GraphItem, int> requiredItem) => itemTypes.Contains(requiredItem.Key.ItemType))); } public void RemoveRecipesForTypes(List<string> itemTypes) { int count = Recipes.Count; Dictionary<string, GraphRecipe> dictionary = Recipes.Where((KeyValuePair<string, GraphRecipe> recipe) => !itemTypes.Contains(recipe.Value.CraftedItem.Item1.ItemType) && !recipe.Value.RequiredItems.Any((KeyValuePair<GraphItem, int> item) => itemTypes.Contains(item.Key.ItemType))).ToDictionary((KeyValuePair<string, GraphRecipe> recipe) => recipe.Key, (KeyValuePair<string, GraphRecipe> recipe) => recipe.Value); Recipes.Clear(); Extensions.AddRange<string, GraphRecipe>(Recipes, dictionary); Logger.LogInfo("reduced '" + Name + "' recipes " + $"from original count {count} to reduced count {Recipes.Count}"); } public override string ToString() { string text = string.Join("\n ", Recipes.Select((KeyValuePair<string, GraphRecipe> recipe) => recipe.Value.ToString().Replace("\n", "\n "))); return "name '" + Name + "' has recipes:\n " + text; } } public class GraphCraftingStation : BaseCrafting { public readonly List<string> ExtensionNames; private GraphCraftingStation(string stationName, List<string> extensionNames) : base(stationName) { ExtensionNames = extensionNames; } public static Dictionary<string, GraphCraftingStation> FromExtensionsAndStations(List<StationExtension> extensions, List<CraftingStation> stations) { Dictionary<string, GraphCraftingStation> stationsFromExtensions = (from extension in extensions select Tuple.Create(((Object)(object)extension.m_craftingStation != (Object)null) ? ((Object)extension.m_craftingStation).name : "missing station", ((Object)extension).name) into tuple group tuple by tuple.Item1).ToDictionary((IGrouping<string, Tuple<string, string>> group) => group.Key, (IGrouping<string, Tuple<string, string>> group) => new GraphCraftingStation(group.Key, group.Select((Tuple<string, string> tuple) => tuple.Item2).ToList())); GraphCraftingStation value; return stations.ToDictionary((CraftingStation station) => ((Object)station).name, (CraftingStation station) => stationsFromExtensions.TryGetValue(((Object)station).name, out value) ? value : new GraphCraftingStation(((Object)station).name, new List<string>())); } public override string ToString() { if (ExtensionNames.Count <= 0) { return "[crafting station " + base.ToString() + "\n]"; } string text = string.Join(", ", ExtensionNames); return "[crafting station " + base.ToString() + "\n has extensions: " + text + "\n]"; } } public class GraphItem { public string ItemName { get; private set; } public string ItemType { get; set; } private GraphItem() { } public static GraphItem GetOrCreate(string itemName, string itemType) { if (DataHarvester.Items.TryGetValue(itemName, out var value)) { return value; } GraphItem graphItem = new GraphItem { ItemName = itemName, ItemType = itemType }; DataHarvester.Items.Add(itemName, graphItem); return graphItem; } public static GraphItem GetOrCreate(ItemDrop drop) { return GetOrCreate(((Object)drop).name, ((object)(ItemType)(ref drop.m_itemData.m_shared.m_itemType)).ToString()); } [CanBeNull] public static GraphItem GetOrCreate(GameObject gameObject) { ItemDrop val = default(ItemDrop); if (!gameObject.TryGetComponent<ItemDrop>(ref val)) { return null; } return GetOrCreate(((Object)val).name, ((object)(ItemType)(ref val.m_itemData.m_shared.m_itemType)).ToString()); } public override string ToString() { return "[item name '" + ItemName + "', type '" + ItemType + "']"; } } public class GraphPiece { public string PieceName; public string RequiredCraftingStation; public Dictionary<GraphItem, int> BuildRequirements; public static GraphPiece FromPiece(Piece fromGame) { return new GraphPiece { PieceName = ((Object)fromGame).name, RequiredCraftingStation = (((Object)(object)fromGame.m_craftingStation == (Object)null) ? "no_station_required" : ((Object)fromGame.m_craftingStation).name), BuildRequirements = fromGame.m_resources.Where((Requirement resource) => (Object)(object)resource.m_resItem != (Object)null).ToDictionary((Requirement resource) => GraphItem.GetOrCreate(((Object)resource.m_resItem).name, ((object)(ItemType)(ref resource.m_resItem.m_itemData.m_shared.m_itemType)).ToString()), (Requirement resource) => resource.m_amount) }; } public override string ToString() { if (BuildRequirements.Count <= 0) { return "[piece name '" + PieceName + "', requires station '" + RequiredCraftingStation + "']"; } string text = string.Join("\n ", BuildRequirements.Select((KeyValuePair<GraphItem, int> requirement) => $"[{requirement.Key} x{requirement.Value}]")); return "[piece name '" + PieceName + "', requires station '" + RequiredCraftingStation + "', requirements:\n " + text + "\n]"; } } public class GraphProcessor : BaseCrafting { private GraphProcessor(string name, Dictionary<string, GraphRecipe> recipes) : base(name, recipes) { } public static GraphProcessor FromSmelter(Smelter smelter) { return new GraphProcessor(((Object)smelter).name, GraphRecipe.FromSmelter(smelter)); } public static GraphProcessor FromIncinerator(Incinerator incinerator) { return new GraphProcessor(((Object)incinerator).name, GraphRecipe.FromIncinerator(incinerator)); } public static GraphProcessor FromFermenter(Fermenter fermenter) { return new GraphProcessor(((Object)fermenter).name, GraphRecipe.FromFermenter(fermenter)); } public static GraphProcessor FromCookingStation(CookingStation cookingStation) { return new GraphProcessor(((Object)cookingStation).name, GraphRecipe.FromCookingStation(cookingStation)); } public override string ToString() { return "[processor " + base.ToString() + "\n]"; } } public class GraphRecipe { public string RecipeName; public Tuple<GraphItem, int> CraftedItem; public Dictionary<GraphItem, int> RequiredItems; private GraphRecipe() { } public static GraphRecipe FromRecipe(Recipe fromGame) { if ((Object)(object)fromGame.m_item == (Object)null) { throw new ArgumentException("recipe '" + ((Object)fromGame).name + "' does not produce an item"); } if (!fromGame.m_resources.ToList().TrueForAll((Requirement recourse) => (Object)(object)recourse.m_resItem != (Object)null)) { throw new ArgumentException("recipe '" + ((Object)fromGame).name + "' contains a null required resource"); } return new GraphRecipe { RecipeName = ((Object)fromGame).name, CraftedItem = Tuple.Create(GraphItem.GetOrCreate(fromGame.m_item), 1), RequiredItems = fromGame.m_resources.ToDictionary((Requirement requirement) => GraphItem.GetOrCreate(requirement.m_resItem), (Requirement requirement) => requirement.m_amount) }; } public static Dictionary<string, GraphRecipe> FromSmelter(Smelter smelter) { string fuel = (((Object)(object)smelter.m_fuelItem == (Object)null) ? "Air" : ((Object)smelter.m_fuelItem).name); return (from recipe in smelter.m_conversion.Select(delegate(ItemConversion conversion) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) GraphRecipe obj = new GraphRecipe { RecipeName = GetProcessorRecipeName(((Object)conversion.m_from).name, ((Object)conversion.m_to).name), CraftedItem = Tuple.Create(GraphItem.GetOrCreate(conversion.m_to), 1) }; Dictionary<GraphItem, int> dictionary = new Dictionary<GraphItem, int>(); string itemName = fuel; ItemType val = (ItemType)1; dictionary.Add(GraphItem.GetOrCreate(itemName, ((object)(ItemType)(ref val)).ToString()), smelter.m_fuelPerProduct); dictionary.Add(GraphItem.GetOrCreate(conversion.m_from), 1); obj.RequiredItems = dictionary; return obj; }) group recipe by recipe.RecipeName).ToDictionary((IGrouping<string, GraphRecipe> group) => group.Key, (IGrouping<string, GraphRecipe> group) => group.First()); } public static Dictionary<string, GraphRecipe> FromIncinerator(Incinerator incinerator) { return (from conversion in incinerator.m_conversions select new GraphRecipe { RecipeName = GetProcessorRecipeName("Incinerator_Sacrifice", ((Object)conversion.m_result).name), CraftedItem = Tuple.Create(GraphItem.GetOrCreate(conversion.m_result), conversion.m_resultAmount), RequiredItems = conversion.m_requirements.ToDictionary((Requirement requirement) => GraphItem.GetOrCreate(requirement.m_resItem), (Requirement requirement) => requirement.m_amount) } into recipe group recipe by recipe.RecipeName).ToDictionary((IGrouping<string, GraphRecipe> group) => group.Key, (IGrouping<string, GraphRecipe> group) => new GraphRecipe { RecipeName = group.Key, CraftedItem = group.First().CraftedItem, RequiredItems = group.SelectMany((GraphRecipe recipe) => recipe.RequiredItems).ToDictionary((KeyValuePair<GraphItem, int> tuple) => tuple.Key, (KeyValuePair<GraphItem, int> tuple) => tuple.Value) }); } public static Dictionary<string, GraphRecipe> FromFermenter(Fermenter fermenter) { return (from conversion in fermenter.m_conversion select new GraphRecipe { RecipeName = GetProcessorRecipeName(((Object)conversion.m_from).name, ((Object)conversion.m_to).name), CraftedItem = Tuple.Create(GraphItem.GetOrCreate(conversion.m_to), conversion.m_producedItems), RequiredItems = new Dictionary<GraphItem, int> { { GraphItem.GetOrCreate(conversion.m_from), 1 } } } into recipe group recipe by recipe.RecipeName).ToDictionary((IGrouping<string, GraphRecipe> group) => group.Key, (IGrouping<string, GraphRecipe> group) => group.First()); } public static Dictionary<string, GraphRecipe> FromCookingStation(CookingStation cookingStation) { string fuel = (((Object)(object)cookingStation.m_fuelItem == (Object)null) ? "Fire" : ((Object)cookingStation.m_fuelItem).name); return (from recipe in cookingStation.m_conversion.Select(delegate(ItemConversion conversion) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) GraphRecipe obj = new GraphRecipe { RecipeName = GetProcessorRecipeName(((Object)conversion.m_from).name, ((Object)conversion.m_to).name), CraftedItem = Tuple.Create(GraphItem.GetOrCreate(conversion.m_to), 1) }; Dictionary<GraphItem, int> obj2 = new Dictionary<GraphItem, int> { { GraphItem.GetOrCreate(conversion.m_from), 1 } }; string itemName = fuel; ItemType val = (ItemType)1; obj2.Add(GraphItem.GetOrCreate(itemName, ((object)(ItemType)(ref val)).ToString()), 1); obj.RequiredItems = obj2; return obj; }) group recipe by recipe.RecipeName).ToDictionary((IGrouping<string, GraphRecipe> group) => group.Key, (IGrouping<string, GraphRecipe> group) => group.First()); } private static string GetProcessorRecipeName(string fromItem, string toItem) { return "[processing " + fromItem + " to " + toItem + "]"; } public override string ToString() { string text = string.Join("\n ", RequiredItems.Select((KeyValuePair<GraphItem, int> requiredItem) => $"[{requiredItem.Key} x{requiredItem.Value}]")); return $"[recipe '{RecipeName}' to create {CraftedItem.Item1} requires:\n" + " " + text + "\n]"; } } } namespace PrefabDependencyTree.Data { public static class DataHarvester { public static readonly Dictionary<string, GraphItem> Items = new Dictionary<string, GraphItem>(); public static readonly Dictionary<string, GraphRecipe> UnboundRecipes = new Dictionary<string, GraphRecipe>(); public static readonly Dictionary<string, GraphCraftingStation> CraftingStations = new Dictionary<string, GraphCraftingStation>(); public static readonly Dictionary<string, GraphProcessor> Processors = new Dictionary<string, GraphProcessor>(); public static readonly Dictionary<Tuple<string, DropType>, List<GraphItem>> Drops = new Dictionary<Tuple<string, DropType>, List<GraphItem>>(); public static readonly Dictionary<string, GraphPiece> Pieces = new Dictionary<string, GraphPiece>(); public static void Initialize() { InitializeSmelters(); InitializeIncinerator(); InitializeCookingStations(); InitializeFermenters(); InitializeCraftingStations(); InitializePieces(); InitializeRecipes(); InitializeDrops(); LogOverview(); LogItemTypesOverview(); } private static void LogOverview() { Logger.LogInfo("vvvv data harvester overview vvvv"); Logger.LogInfo($" total {Items.Count} items registered"); Logger.LogInfo($" total {Drops.Count} drops registered"); Logger.LogInfo($" total {Pieces.Count} pieces registered"); Logger.LogInfo($" total {CraftingStations.Count} crafting stations registered"); Logger.LogInfo($" total {Processors.Count} processor stations (fermenter, smelter, ...) registered"); Logger.LogInfo($" total {UnboundRecipes.Count} unbound recipes registered"); Logger.LogInfo("^^^^ data harvester overview ^^^^"); } private static void LogItemTypesOverview() { Dictionary<string, int> source = (from item in Items group item by item.Value.ItemType).ToDictionary((IGrouping<string, KeyValuePair<string, GraphItem>> group) => group.Key, (IGrouping<string, KeyValuePair<string, GraphItem>> group) => group.Count()); Logger.LogInfo("vvvv item types overview vvvv"); foreach (KeyValuePair<string, int> item in source.OrderBy((KeyValuePair<string, int> pair) => pair.Key)) { Logger.LogInfo($"item type '{item.Key}' -> found {item.Value} items"); } Logger.LogInfo("^^^^ item types overview ^^^^"); } public static List<string> LogAllToString() { List<string> list = new List<string>(); list.Add("==== debug log output for all prefabs ===="); list.AddRange(Items.Select((KeyValuePair<string, GraphItem> item) => item.Value.ToString())); list.AddRange(from <>h__TransparentIdentifier0 in Drops.Select(delegate(KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop) { KeyValuePair<Tuple<string, DropType>, List<GraphItem>> keyValuePair = drop; return new { drop = drop, dropsList = string.Join("\n ", keyValuePair.Value.Select((GraphItem item) => item.ToString())) }; }) select $"['{<>h__TransparentIdentifier0.drop.Key}' drops:\n" + " " + <>h__TransparentIdentifier0.dropsList + "\n]"); list.AddRange(Pieces.Select((KeyValuePair<string, GraphPiece> piece) => piece.Value.ToString())); list.AddRange(CraftingStations.Select((KeyValuePair<string, GraphCraftingStation> station) => station.Value.ToString())); list.AddRange(Processors.Select((KeyValuePair<string, GraphProcessor> processor) => processor.Value.ToString())); list.AddRange(UnboundRecipes.Select((KeyValuePair<string, GraphRecipe> recipe) => recipe.Value.ToString())); list.Add("==== debug log output for all prefabs ===="); return list; } public static List<string> LogAllItemsToCategorizedYaml() { List<GraphItem> second = CraftingStations.SelectMany((KeyValuePair<string, GraphCraftingStation> cs) => cs.Value.Recipes.Select((KeyValuePair<string, GraphRecipe> recipe) => recipe.Value).ToList()).Union(UnboundRecipes.Select((KeyValuePair<string, GraphRecipe> unbound) => unbound.Value)).Union(Processors.SelectMany((KeyValuePair<string, GraphProcessor> processor) => processor.Value.Recipes.Select((KeyValuePair<string, GraphRecipe> recipe) => recipe.Value).ToList())) .ToList() .SelectMany(delegate(GraphRecipe recipe) { List<GraphItem> list2 = new List<GraphItem>(); list2.Add(recipe.CraftedItem.Item1); list2.AddRange(recipe.RequiredItems.Select((KeyValuePair<GraphItem, int> req) => req.Key)); return list2; }) .ToList(); Dictionary<string, List<string>> dictionary = (from item in Items.Select((KeyValuePair<string, GraphItem> item) => item.Value).Union(second).Union(Drops.SelectMany((KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop) => drop.Value)) .ToList() group item by item.ItemType).ToDictionary((IGrouping<string, GraphItem> group) => group.Key, (IGrouping<string, GraphItem> group) => (from item in @group.Select((GraphItem item) => item.ItemName).Distinct() orderby item select item).ToList()); List<string> list = new List<string>(); foreach (KeyValuePair<string, List<string>> item in dictionary) { list.Add(item.Key + ":"); list.AddRange(item.Value.Select((string item) => " - " + item)); } return list; } private static void InitializePieces() { Dictionary<string, GraphPiece> dictionary = Cache.GetPrefabs(typeof(Piece)).ToDictionary((KeyValuePair<string, Object> kv) => kv.Key, (KeyValuePair<string, Object> kv) => GraphPiece.FromPiece((Piece)kv.Value)); dictionary.Where((KeyValuePair<string, GraphPiece> kv) => !CraftingStations.ContainsKey(kv.Key) && !Processors.ContainsKey(kv.Key)).ToList().ForEach(delegate(KeyValuePair<string, GraphPiece> piece) { Pieces.Add(piece.Key, piece.Value); }); Logger.LogInfo($"loaded {dictionary.Count} pieces from game"); } private static void InitializeSmelters() { Dictionary<string, GraphProcessor> dictionary = Cache.GetPrefabs(typeof(Smelter)).ToDictionary((KeyValuePair<string, Object> kv) => kv.Key, (KeyValuePair<string, Object> kv) => GraphProcessor.FromSmelter((Smelter)kv.Value)); dictionary.ToList().ForEach(delegate(KeyValuePair<string, GraphProcessor> smelter) { Processors.Add(smelter.Key, smelter.Value); }); int num = dictionary.Select((KeyValuePair<string, GraphProcessor> smelter) => smelter.Value.Recipes.Count).Sum(); Logger.LogInfo($"loaded {dictionary.Count} smelters with {num} conversions from game"); } private static void InitializeIncinerator() { Dictionary<string, GraphProcessor> dictionary = Cache.GetPrefabs(typeof(Incinerator)).ToDictionary((KeyValuePair<string, Object> kv) => kv.Key, (KeyValuePair<string, Object> kv) => GraphProcessor.FromIncinerator((Incinerator)kv.Value)); dictionary.ToList().ForEach(delegate(KeyValuePair<string, GraphProcessor> incinerator) { Processors.Add(incinerator.Key, incinerator.Value); }); int num = dictionary.Select((KeyValuePair<string, GraphProcessor> incinerator) => incinerator.Value.Recipes.Count).Sum(); Logger.LogInfo($"loaded {dictionary.Count} incinerators with {num} conversions from game"); } private static void InitializeCookingStations() { Dictionary<string, GraphProcessor> dictionary = Cache.GetPrefabs(typeof(CookingStation)).ToDictionary((KeyValuePair<string, Object> kv) => kv.Key, (KeyValuePair<string, Object> kv) => GraphProcessor.FromCookingStation((CookingStation)kv.Value)); dictionary.ToList().ForEach(delegate(KeyValuePair<string, GraphProcessor> fermenter) { Processors.Add(fermenter.Key, fermenter.Value); }); int num = dictionary.Select((KeyValuePair<string, GraphProcessor> smelter) => smelter.Value.Recipes.Count).Sum(); Logger.LogInfo($"loaded {dictionary.Count} cooking stations with {num} conversions from game"); } private static void InitializeFermenters() { Dictionary<string, GraphProcessor> dictionary = Cache.GetPrefabs(typeof(Fermenter)).ToDictionary((KeyValuePair<string, Object> kv) => kv.Key, (KeyValuePair<string, Object> kv) => GraphProcessor.FromFermenter((Fermenter)kv.Value)); dictionary.ToList().ForEach(delegate(KeyValuePair<string, GraphProcessor> fermenter) { Processors.Add(fermenter.Key, fermenter.Value); }); int num = dictionary.Select((KeyValuePair<string, GraphProcessor> smelter) => smelter.Value.Recipes.Count).Sum(); Logger.LogInfo($"loaded {dictionary.Count} fermenters with {num} conversions from game"); } private static void InitializeCraftingStations() { Dictionary<string, GraphCraftingStation> dictionary = GraphCraftingStation.FromExtensionsAndStations(((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(StationExtension))).Select((Func<KeyValuePair<string, Object>, StationExtension>)((KeyValuePair<string, Object> kv) => (StationExtension)kv.Value)).ToList(), ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(CraftingStation))).Select((Func<KeyValuePair<string, Object>, CraftingStation>)((KeyValuePair<string, Object> kv) => (CraftingStation)kv.Value)).ToList()); dictionary.ToList().ForEach(delegate(KeyValuePair<string, GraphCraftingStation> station) { CraftingStations.Add(station.Key, station.Value); }); Logger.LogInfo($"loaded {dictionary.Count} crafting stations from game"); } private static void InitializeRecipes() { foreach (KeyValuePair<string, Recipe> item in ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(Recipe))).ToDictionary((Func<KeyValuePair<string, Object>, string>)((KeyValuePair<string, Object> kv) => kv.Key), (Func<KeyValuePair<string, Object>, Recipe>)((KeyValuePair<string, Object> kv) => (Recipe)kv.Value))) { if ((Object)(object)item.Value.m_item == (Object)null) { Logger.LogInfo("recipe '" + item.Key + "' does not create an item - skipping"); } else if ((Object)(object)item.Value.m_craftingStation != (Object)null) { if (CraftingStations.TryGetValue(((Object)item.Value.m_craftingStation).name, out var value)) { value.Recipes.Add(item.Key, GraphRecipe.FromRecipe(item.Value)); continue; } Logger.LogWarning("recipe '" + item.Key + "': station '" + ((Object)item.Value.m_craftingStation).name + "' not found"); } else { UnboundRecipes.Add(item.Key, GraphRecipe.FromRecipe(item.Value)); } } int num = CraftingStations.Select((KeyValuePair<string, GraphCraftingStation> station) => station.Value.Recipes.Count).Sum(); Logger.LogInfo($"loaded {num} recipes bound to crafting stations from game"); Logger.LogInfo($"loaded {UnboundRecipes.Count} recipes from game that are not bound to a crafting station"); } private static void InitializeDrops() { List<Initializer> list = new List<Initializer>(); list.Add(new ContainerDropsInitializer()); list.Add(new DestructibleDropsInitializer()); list.Add(new TreeLogDropsInitializer()); list.Add(new TreeBaseDropsInitializer()); list.Add(new CharacterDropInitializer()); list.Add(new MineRockDropsInitializer()); list.Add(new MineRock5DropInitializer()); list.Add(new LootSpawnerDropsInitializer()); list.Add(new PickableDropInitializer()); list.Add(new PickableExtraDropInitializer()); list.Add(new PickableItemDropInitializer()); list.Add(new PickableItemRandomDropInitializer()); list.ForEach(delegate(Initializer initializer) { try { initializer.InitializeDrops(); } catch (Exception ex) { Logger.LogError("got exception on initializing: " + ex.Message); Logger.LogError(ex.StackTrace); } }); } } public class GraphBuilder { private readonly DotGraph Graph = new DotGraph().WithIdentifier("graph").Directed(); private readonly Dictionary<string, DotNode> Nodes = new Dictionary<string, DotNode>(); private readonly Dictionary<string, DotEdge> Edges = new Dictionary<string, DotEdge>(); public void AddNode(GraphItem item) { AddNode(item.ItemName, item.ItemType); } public void AddNode(string name, string nodeType) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(name)) { Logger.LogWarning("tried adding empty node name"); } else { if (Nodes.ContainsKey(name)) { return; } DotNode dotNode = new DotNode().WithIdentifier(name).WithLabel(name).WithShape(DotNodeShape.Rectangle); if (nodeType != null) { DropType result2; if (Enum.TryParse<ItemType>(nodeType, out ItemType result)) { SetNodeColorItemType(dotNode, result); } else if (Enum.TryParse<DropType>(nodeType, out result2)) { SetNodeColorDropType(dotNode, result2); } else { if (!Enum.TryParse<NodeType>(nodeType, out var result3)) { throw new ArgumentException("node type '" + nodeType + "' does not match any types known"); } SetNodePieceTypeOption(dotNode, result3); } } Nodes.Add(name, dotNode); } } private static void SetNodeColorDropType(DotNode node, DropType dropType) { switch (dropType) { case DropType.Character: node.WithColor(DotColor.Orange); break; case DropType.Container: case DropType.LootSpawner: node.WithColor(DotColor.Beige); break; case DropType.Destructible: node.WithColor(DotColor.Crimson); break; case DropType.Pickable: node.WithColor(DotColor.Ivory); break; case DropType.Tree: node.WithColor(DotColor.Brown); break; case DropType.MineRock: node.WithShape(DotNodeShape.Diamond); node.WithColor(DotColor.SlateGrey); break; default: Logger.LogWarning("node type '" + dropType.ToString() + "' not supported"); break; } } private static void SetNodeColorItemType(DotNode node, ItemType itemType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected I4, but got Unknown switch ((int)itemType) { case 9: case 23: node.WithColor(DotColor.Cyan); break; case 6: case 7: case 11: case 12: case 17: node.WithColor(DotColor.DarkGrey); node.WithFontColor(DotColor.White); break; case 15: case 18: case 19: node.WithColor(DotColor.Indigo); break; case 3: case 4: case 5: case 14: case 20: case 22: node.WithColor(DotColor.IndianRed); break; case 2: node.WithColor(DotColor.LightGreen); break; case 21: node.WithColor(DotColor.LightBlue); break; case 1: node.WithColor(DotColor.SandyBrown); break; case 10: case 16: node.WithColor(DotColor.Grey); node.WithFontColor(DotColor.White); break; case 0: node.WithColor(DotColor.Yellow); break; case 13: node.WithColor(DotColor.Violet); node.WithFontColor(DotColor.White); break; default: throw new ArgumentException("type '" + ((object)(ItemType)(ref itemType)).ToString() + "' not supported"); } } private static void SetNodePieceTypeOption(DotNode node, NodeType nodeType) { switch (nodeType) { case NodeType.Piece: node.WithShape(DotNodeShape.Assembly); node.WithColor(DotColor.Brown); break; case NodeType.Processor: node.WithShape(DotNodeShape.Circle); node.WithColor(DotColor.Gold); break; case NodeType.CraftingStation: node.WithShape(DotNodeShape.Circle); node.WithColor(DotColor.Gold); break; case NodeType.Recipe: node.WithShape(DotNodeShape.Note); node.WithColor(DotColor.Cornsilk); break; default: throw new ArgumentException("type '" + nodeType.ToString() + "' not supported"); } } public void AddEdge(string from, string to) { string key = from + "->" + to; if (!Edges.ContainsKey(key)) { Edges.Add(key, new DotEdge().From(from).To(to)); } } public DotGraph BuildGraph() { Nodes.ToList().ForEach(delegate(KeyValuePair<string, DotNode> node) { Graph.Add(node.Value); }); Edges.ToList().ForEach(delegate(KeyValuePair<string, DotEdge> edge) { Graph.Add(edge.Value); }); return Graph; } } public enum NodeType { Piece, CraftingStation, Processor, Recipe } public enum DropType { Character, Container, Destructible, LootSpawner, MineRock, Pickable, Tree } public enum FilterType { Include, Exclude, Unfiltered } } namespace PrefabDependencyTree.Data.Filters { public class ExcludeFilterTypes : FilteredData { public ExcludeFilterTypes(List<string> itemTypeFilters) { ItemTypeFilters = itemTypeFilters; FilterType = FilterType.Exclude; Items = DataHarvester.Items.Where((KeyValuePair<string, GraphItem> item) => !itemTypeFilters.Contains(item.Value.ItemType)).ToDictionary((KeyValuePair<string, GraphItem> pair) => pair.Key, (KeyValuePair<string, GraphItem> pair) => pair.Value); UnboundRecipes = DataHarvester.UnboundRecipes.Where((KeyValuePair<string, GraphRecipe> recipe) => !itemTypeFilters.Contains(recipe.Value.CraftedItem.Item1.ItemType) && !recipe.Value.RequiredItems.Any((KeyValuePair<GraphItem, int> requiredItem) => itemTypeFilters.Contains(requiredItem.Key.ItemType))).ToDictionary((KeyValuePair<string, GraphRecipe> pair) => pair.Key, (KeyValuePair<string, GraphRecipe> pair) => pair.Value); CraftingStations = (from station in DataHarvester.CraftingStations.ToDictionary((KeyValuePair<string, GraphCraftingStation> station) => station.Key, delegate(KeyValuePair<string, GraphCraftingStation> station) { station.Value.RemoveRecipesForTypes(itemTypeFilters); return station.Value; }) where station.Value.Recipes.Count > 0 select station).ToDictionary((KeyValuePair<string, GraphCraftingStation> kv) => kv.Key, (KeyValuePair<string, GraphCraftingStation> kv) => kv.Value); Processors = (from processor in DataHarvester.Processors.ToDictionary((KeyValuePair<string, GraphProcessor> processor) => processor.Key, delegate(KeyValuePair<string, GraphProcessor> processor) { processor.Value.RemoveRecipesForTypes(itemTypeFilters); return processor.Value; }) where processor.Value.Recipes.Count > 0 select processor).ToDictionary((KeyValuePair<string, GraphProcessor> kv) => kv.Key, (KeyValuePair<string, GraphProcessor> kv) => kv.Value); Drops = (from drop in DataHarvester.Drops.ToDictionary((KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop) => drop.Key, delegate(KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop) { List<GraphItem> collection = drop.Value.Where((GraphItem item) => !itemTypeFilters.Contains(item.ItemType)).ToList(); drop.Value.Clear(); drop.Value.AddRange(collection); return drop.Value; }) where drop.Value.Count > 0 select drop).ToDictionary((KeyValuePair<Tuple<string, DropType>, List<GraphItem>> kv) => kv.Key, (KeyValuePair<Tuple<string, DropType>, List<GraphItem>> kv) => kv.Value); Pieces = DataHarvester.Pieces.Where((KeyValuePair<string, GraphPiece> piece) => !piece.Value.BuildRequirements.Any((KeyValuePair<GraphItem, int> requirement) => itemTypeFilters.Contains(requirement.Key.ItemType))).ToDictionary((KeyValuePair<string, GraphPiece> pair) => pair.Key, (KeyValuePair<string, GraphPiece> pair) => pair.Value); } } public abstract class FilteredData { protected Dictionary<string, GraphItem> Items; protected Dictionary<string, GraphRecipe> UnboundRecipes; protected Dictionary<string, GraphCraftingStation> CraftingStations; protected Dictionary<string, GraphProcessor> Processors; protected Dictionary<Tuple<string, DropType>, List<GraphItem>> Drops; protected Dictionary<string, GraphPiece> Pieces; protected List<string> ItemTypeFilters; protected FilterType FilterType; public void LogOverview() { Logger.LogInfo("vvvv filtered data overview vvvv"); Logger.LogInfo(" applied " + FilterType.ToString() + " filters: " + string.Join(", ", ItemTypeFilters)); Logger.LogInfo($" total {Items.Count} items registered"); Logger.LogInfo($" total {Drops.Count} drops registered"); Logger.LogInfo($" total {Pieces.Count} pieces registered"); Logger.LogInfo($" total {CraftingStations.Count} crafting stations registered"); Logger.LogInfo($" total {Processors.Count} processor stations (fermenter, smelter, ...) registered"); Logger.LogInfo($" total {UnboundRecipes.Count} unbound recipes registered"); Logger.LogInfo("^^^^ filtered data overview ^^^^"); } public DotGraph CreateGraph() { GraphBuilder graphBuilder = new GraphBuilder(); Items.ToList().ForEach(delegate(KeyValuePair<string, GraphItem> item) { graphBuilder.AddNode(item.Value.ItemName, item.Value.ItemType); }); foreach (KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop in Drops) { graphBuilder.AddNode(drop.Key.Item1, drop.Key.Item2.ToString()); foreach (GraphItem item in drop.Value) { graphBuilder.AddNode(item); graphBuilder.AddEdge(drop.Key.Item1, item.ItemName); } } AddRecipesToGraph(graphBuilder, UnboundRecipes); foreach (KeyValuePair<string, GraphCraftingStation> craftingStation in CraftingStations) { graphBuilder.AddNode(craftingStation.Value.Name, NodeType.CraftingStation.ToString()); foreach (string extensionName in craftingStation.Value.ExtensionNames) { graphBuilder.AddNode(extensionName, NodeType.CraftingStation.ToString()); graphBuilder.AddEdge(extensionName, craftingStation.Value.Name); } AddRecipesToGraph(graphBuilder, craftingStation.Value.Recipes); } foreach (KeyValuePair<string, GraphProcessor> processor in Processors) { graphBuilder.AddNode(processor.Value.Name, NodeType.Processor.ToString()); AddRecipesToGraph(graphBuilder, processor.Value.Recipes); } foreach (KeyValuePair<string, GraphPiece> piece in Pieces) { graphBuilder.AddNode(piece.Value.PieceName, NodeType.Piece.ToString()); graphBuilder.AddNode(piece.Value.RequiredCraftingStation, NodeType.CraftingStation.ToString()); graphBuilder.AddEdge(piece.Value.RequiredCraftingStation, piece.Value.PieceName); foreach (KeyValuePair<GraphItem, int> buildRequirement in piece.Value.BuildRequirements) { graphBuilder.AddNode(buildRequirement.Key); graphBuilder.AddEdge(buildRequirement.Key.ItemName, piece.Value.PieceName); } } return graphBuilder.BuildGraph(); } private static void AddRecipesToGraph(GraphBuilder graphBuilder, Dictionary<string, GraphRecipe> recipes) { foreach (KeyValuePair<string, GraphRecipe> recipe in recipes) { graphBuilder.AddNode(recipe.Value.RecipeName, NodeType.Recipe.ToString()); graphBuilder.AddNode(recipe.Value.CraftedItem.Item1); graphBuilder.AddEdge(recipe.Value.RecipeName, recipe.Value.CraftedItem.Item1.ItemName); foreach (KeyValuePair<GraphItem, int> requiredItem in recipe.Value.RequiredItems) { graphBuilder.AddNode(requiredItem.Key); graphBuilder.AddEdge(requiredItem.Key.ItemName, recipe.Value.RecipeName); } } } } public class IncludeFilterTypes : FilteredData { public IncludeFilterTypes(List<string> itemTypeFilters) { ItemTypeFilters = itemTypeFilters; FilterType = FilterType.Include; Items = DataHarvester.Items.Where((KeyValuePair<string, GraphItem> item) => itemTypeFilters.Contains(item.Value.ItemType)).ToDictionary((KeyValuePair<string, GraphItem> pair) => pair.Key, (KeyValuePair<string, GraphItem> pair) => pair.Value); UnboundRecipes = DataHarvester.UnboundRecipes.Where((KeyValuePair<string, GraphRecipe> recipe) => itemTypeFilters.Contains(recipe.Value.CraftedItem.Item1.ItemType) || recipe.Value.RequiredItems.Any((KeyValuePair<GraphItem, int> requiredItem) => itemTypeFilters.Contains(requiredItem.Key.ItemType))).ToDictionary((KeyValuePair<string, GraphRecipe> pair) => pair.Key, (KeyValuePair<string, GraphRecipe> pair) => pair.Value); CraftingStations = DataHarvester.CraftingStations.Where((KeyValuePair<string, GraphCraftingStation> station) => station.Value.ContainsItemTypes(itemTypeFilters)).ToDictionary((KeyValuePair<string, GraphCraftingStation> pair) => pair.Key, (KeyValuePair<string, GraphCraftingStation> pair) => pair.Value); Processors = DataHarvester.Processors.Where((KeyValuePair<string, GraphProcessor> processor) => processor.Value.ContainsItemTypes(itemTypeFilters)).ToDictionary((KeyValuePair<string, GraphProcessor> pair) => pair.Key, (KeyValuePair<string, GraphProcessor> pair) => pair.Value); Drops = DataHarvester.Drops.Where((KeyValuePair<Tuple<string, DropType>, List<GraphItem>> drop) => drop.Value.Any((GraphItem item) => itemTypeFilters.Contains(item.ItemType))).ToDictionary((KeyValuePair<Tuple<string, DropType>, List<GraphItem>> pair) => pair.Key, (KeyValuePair<Tuple<string, DropType>, List<GraphItem>> pair) => pair.Value); Pieces = DataHarvester.Pieces.Where((KeyValuePair<string, GraphPiece> piece) => piece.Value.BuildRequirements.Any((KeyValuePair<GraphItem, int> requirement) => itemTypeFilters.Contains(requirement.Key.ItemType))).ToDictionary((KeyValuePair<string, GraphPiece> pair) => pair.Key, (KeyValuePair<string, GraphPiece> pair) => pair.Value); } } public class UnfilteredData : FilteredData { public UnfilteredData() { FilterType = FilterType.Unfiltered; ItemTypeFilters = new List<string>(); Items = DataHarvester.Items; UnboundRecipes = DataHarvester.UnboundRecipes; CraftingStations = DataHarvester.CraftingStations; Processors = DataHarvester.Processors; Drops = DataHarvester.Drops; Pieces = DataHarvester.Pieces; } } } namespace PrefabDependencyTree.Data.Drops { public class CharacterDropInitializer : DropsInitializer<CharacterDrop, Drop> { protected override Dictionary<Tuple<string, DropType>, CharacterDrop> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(Character))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Character)), (Func<KeyValuePair<string, Object>, CharacterDrop>)((KeyValuePair<string, Object> kv) => (CharacterDrop)((Component)(Character)kv.Value).GetComponent(typeof(CharacterDrop)))); } protected override List<Drop> GetDropList(CharacterDrop input) { if ((Object)(object)input != (Object)null && input.m_drops != null) { return input.m_drops.ToList(); } return new List<Drop>(); } protected override GameObject GetObject(Drop input) { return input.m_prefab; } } public class ContainerDropsInitializer : DropsInitializerDropData<Container> { protected override Dictionary<Tuple<string, DropType>, Container> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(Container))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Container)), (Func<KeyValuePair<string, Object>, Container>)((KeyValuePair<string, Object> kv) => (Container)kv.Value)); } protected override List<DropData> GetDropList(Container input) { if (input.m_defaultItems != null) { return FromTable(input.m_defaultItems); } Logger.LogWarning($"'m_defaultItems' not found for {typeof(Container)}"); return new List<DropData>(); } } public class DestructibleDropsInitializer : DropsInitializerDropData<DropOnDestroyed> { protected override Dictionary<Tuple<string, DropType>, DropOnDestroyed> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(DropOnDestroyed))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Destructible)), (Func<KeyValuePair<string, Object>, DropOnDestroyed>)((KeyValuePair<string, Object> kv) => (DropOnDestroyed)kv.Value)); } protected override List<DropData> GetDropList(DropOnDestroyed input) { if (input.m_dropWhenDestroyed != null) { return FromTable(input.m_dropWhenDestroyed); } Logger.LogWarning($"'m_dropWhenDestroyed' not found for {typeof(DropOnDestroyed)}"); return new List<DropData>(); } } public class LootSpawnerDropsInitializer : DropsInitializerDropData<LootSpawner> { protected override Dictionary<Tuple<string, DropType>, LootSpawner> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(LootSpawner))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.LootSpawner)), (Func<KeyValuePair<string, Object>, LootSpawner>)((KeyValuePair<string, Object> kv) => (LootSpawner)kv.Value)); } protected override List<DropData> GetDropList(LootSpawner input) { if (input.m_items != null) { return FromTable(input.m_items); } Logger.LogWarning($"'m_items' not found for {typeof(LootSpawner)}"); return new List<DropData>(); } } public class MineRock5DropInitializer : DropsInitializerDropData<MineRock5> { protected override Dictionary<Tuple<string, DropType>, MineRock5> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(MineRock5))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.MineRock)), (Func<KeyValuePair<string, Object>, MineRock5>)((KeyValuePair<string, Object> kv) => (MineRock5)kv.Value)); } protected override List<DropData> GetDropList(MineRock5 input) { if (input.m_dropItems != null) { return FromTable(input.m_dropItems); } Logger.LogWarning($"'m_dropItems' not found for {typeof(MineRock5)}"); return new List<DropData>(); } } public class MineRockDropsInitializer : DropsInitializerDropData<MineRock> { protected override Dictionary<Tuple<string, DropType>, MineRock> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(MineRock))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.MineRock)), (Func<KeyValuePair<string, Object>, MineRock>)((KeyValuePair<string, Object> kv) => (MineRock)kv.Value)); } protected override List<DropData> GetDropList(MineRock input) { if (input.m_dropItems != null) { return FromTable(input.m_dropItems); } Logger.LogWarning($"'m_dropItems' not found for {typeof(MineRock)}"); return new List<DropData>(); } } public class PickableDropInitializer : DropsInitializer<Pickable, GameObject> { protected override Dictionary<Tuple<string, DropType>, Pickable> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(Pickable))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Pickable)), (Func<KeyValuePair<string, Object>, Pickable>)((KeyValuePair<string, Object> kv) => (Pickable)kv.Value)); } protected override List<GameObject> GetDropList(Pickable input) { return new List<GameObject> { input.m_itemPrefab }; } protected override GameObject GetObject(GameObject input) { return input; } } public class PickableExtraDropInitializer : DropsInitializerDropData<Pickable> { protected override Dictionary<Tuple<string, DropType>, Pickable> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(Pickable))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Pickable)), (Func<KeyValuePair<string, Object>, Pickable>)((KeyValuePair<string, Object> kv) => (Pickable)kv.Value)); } protected override List<DropData> GetDropList(Pickable input) { if (input.m_extraDrops != null) { return FromTable(input.m_extraDrops); } Logger.LogWarning($"'m_extraDrops' not found for {typeof(Pickable)}"); return new List<DropData>(); } } public class PickableItemDropInitializer : DropsInitializer<PickableItem, ItemDrop> { protected override Dictionary<Tuple<string, DropType>, PickableItem> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(PickableItem))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Pickable)), (Func<KeyValuePair<string, Object>, PickableItem>)((KeyValuePair<string, Object> kv) => (PickableItem)kv.Value)); } protected override List<ItemDrop> GetDropList(PickableItem input) { return new List<ItemDrop> { input.m_itemPrefab }; } protected override GameObject GetObject(ItemDrop input) { return ((Component)input).gameObject; } } public class PickableItemRandomDropInitializer : DropsInitializer<PickableItem, RandomItem> { protected override Dictionary<Tuple<string, DropType>, PickableItem> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(PickableItem))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Pickable)), (Func<KeyValuePair<string, Object>, PickableItem>)((KeyValuePair<string, Object> kv) => (PickableItem)kv.Value)); } protected override List<RandomItem> GetDropList(PickableItem input) { if (input.m_randomItemPrefabs != null) { return input.m_randomItemPrefabs.ToList(); } Logger.LogWarning($"'m_randomItemPrefabs' not found for {typeof(PickableItem)}"); return new List<RandomItem>(); } protected override GameObject GetObject(RandomItem input) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ((Component)input.m_itemPrefab).gameObject; } } public class TreeBaseDropsInitializer : DropsInitializerDropData<TreeBase> { protected override Dictionary<Tuple<string, DropType>, TreeBase> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(TreeBase))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Tree)), (Func<KeyValuePair<string, Object>, TreeBase>)((KeyValuePair<string, Object> kv) => (TreeBase)kv.Value)); } protected override List<DropData> GetDropList(TreeBase input) { if (input.m_dropWhenDestroyed != null) { return FromTable(input.m_dropWhenDestroyed); } Logger.LogWarning($"'m_dropWhenDestroyed' not found for {typeof(TreeBase)}"); return new List<DropData>(); } } public class TreeLogDropsInitializer : DropsInitializerDropData<TreeLog> { protected override Dictionary<Tuple<string, DropType>, TreeLog> GetGameObjects() { return ((IEnumerable<KeyValuePair<string, Object>>)Cache.GetPrefabs(typeof(TreeLog))).ToDictionary((Func<KeyValuePair<string, Object>, Tuple<string, DropType>>)((KeyValuePair<string, Object> kv) => Tuple.Create(kv.Key, DropType.Tree)), (Func<KeyValuePair<string, Object>, TreeLog>)((KeyValuePair<string, Object> kv) => (TreeLog)kv.Value)); } protected override List<DropData> GetDropList(TreeLog input) { if (input.m_dropWhenDestroyed != null) { return FromTable(input.m_dropWhenDestroyed); } Logger.LogWarning($"'m_dropWhenDestroyed' not found for {typeof(TreeLog)}"); return new List<DropData>(); } } } namespace PrefabDependencyTree.Data.Drops.Generic { public abstract class DropsInitializer<T, U> : Initializer { protected abstract Dictionary<Tuple<string, DropType>, T> GetGameObjects(); protected abstract List<U> GetDropList(T input); protected abstract GameObject GetObject(U input); public void InitializeDrops() { int count = DataHarvester.Drops.Count; foreach (KeyValuePair<Tuple<string, DropType>, T> gameObject in GetGameObjects()) { List<U> list = (from drop in GetDropList(gameObject.Value) where drop != null select drop).ToList(); if (list.Count <= 0) { Logger.LogInfo($"{typeof(T)} '{gameObject.Key}' does not have drop data - skipping"); continue; } List<GraphItem> value; List<GraphItem> list2 = (DataHarvester.Drops.TryGetValue(gameObject.Key, out value) ? value : new List<GraphItem>()); foreach (U item in list) { if (item == null) { Logger.LogInfo($"{typeof(T)} - {typeof(U)}: found null drop in drop data (length {list.Count})"); continue; } object obj = item; ItemDrop val = (ItemDrop)((obj is ItemDrop) ? obj : null); if (val != null) { list2.Add(GraphItem.GetOrCreate(val)); continue; } GraphItem orCreate = GraphItem.GetOrCreate(GetObject(item)); if (orCreate != null) { list2.Add(orCreate); } } DataHarvester.Drops[Tuple.Create(gameObject.Key.Item1, gameObject.Key.Item2)] = list2.Distinct().ToList(); } Logger.LogInfo($"loaded {DataHarvester.Drops.Count - count} drops from {typeof(T)} ({typeof(U)}) from game"); } } public abstract class DropsInitializerDropData<T> : DropsInitializer<T, DropData> { protected override GameObject GetObject(DropData input) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return input.m_item; } protected List<DropData> FromTable(DropTable table) { if (table != null) { return table.m_drops; } Logger.LogWarning($"missing drop table for type '{typeof(T)}'"); return new List<DropData>(); } } public interface Initializer { void InitializeDrops(); } } namespace DotNetGraph.Extensions { internal static class DotBaseGraphExtensions { public static T WithIdentifier<T>(this T graph, string identifier, bool isHtml = false) where T : DotBaseGraph { graph.Identifier = new DotIdentifier(identifier, isHtml); return graph; } public static T WithRankDir<T>(this T graph, DotRankDir rankDir) where T : DotBaseGraph { graph.RankDir = new DotRankDirAttribute(rankDir); return graph; } public static T Add<T>(this T graph, IDotElement element) where T : DotBaseGraph { graph.Elements.Add(element); return graph; } } internal static class DotEdgeExtensions { public static DotEdge From(this DotEdge edge, string from, bool isHtml = false) { edge.From = new DotIdentifier(from, isHtml); return edge; } public static DotEdge From(this DotEdge edge, DotNode from) { edge.From = from.Identifier; return edge; } public static DotEdge To(this DotEdge edge, string to, bool isHtml = false) { edge.To = new DotIdentifier(to, isHtml); return edge; } public static DotEdge To(this DotEdge edge, DotNode to) { edge.To = to.Identifier; return edge; } public static DotEdge WithColor(this DotEdge edge, string color) { edge.Color = new DotColorAttribute(color); return edge; } public static DotEdge WithColor(this DotEdge edge, DotColor color) { edge.Color = new DotColorAttribute(color); return edge; } public static DotEdge WithStyle(this DotEdge edge, string style) { edge.Style = new DotEdgeStyleAttribute(style); return edge; } public static DotEdge WithStyle(this DotEdge edge, DotEdgeStyle style) { edge.Style = new DotEdgeStyleAttribute(style); return edge; } public static DotEdge WithPenWidth(this DotEdge edge, double width) { edge.PenWidth = new DotDoubleAttribute(width); return edge; } public static DotEdge WithArrowHead(this DotEdge edge, string arrowType) { edge.ArrowHead = new DotEdgeArrowTypeAttribute(arrowType); return edge; } public static DotEdge WithArrowHead(this DotEdge edge, DotEdgeArrowType arrowType) { edge.ArrowHead = new DotEdgeArrowTypeAttribute(arrowType); return edge; } public static DotEdge WithArrowTail(this DotEdge edge, string arrowType) { edge.ArrowTail = new DotEdgeArrowTypeAttribute(arrowType); return edge; } public static DotEdge WithArrowTail(this DotEdge edge, DotEdgeArrowType arrowType) { edge.ArrowTail = new DotEdgeArrowTypeAttribute(arrowType); return edge; } public static DotEdge WithPos(this DotEdge edge, string value) { edge.Pos = new DotPointAttribute(value); return edge; } public static DotEdge WithPos(this DotEdge edge, int x, int y, bool @fixed = false) { edge.Pos = new DotPointAttribute(new DotPoint(x, y, @fixed)); return edge; } public static DotEdge WithPos(this DotEdge edge, int x, int y, int z, bool @fixed = false) { edge.Pos = new DotPointAttribute(new DotPoint(x, y, z, @fixed)); return edge; } } internal static class DotElementExtensions { public static T WithAttribute<T>(this T element, string name, IDotAttribute attribute) where T : DotElement { element.SetAttribute(name, attribute); return element; } public static T WithAttribute<T>(this T element, string name, string value) where T : DotElement { element.SetAttribute(name, new DotAttribute(value)); return element; } public static T WithLabel<T>(this T element, string label, bool isHtml = false) where T : DotElement { element.Label = new DotLabelAttribute(label, isHtml); return element; } public static T WithFontColor<T>(this T element, string color) where T : DotElement { element.FontColor = new DotColorAttribute(color); return element; } public static T WithFontColor<T>(this T element, DotColor color) where T : DotElement { element.FontColor = new DotColorAttribute(color); return element; } } internal static class DotGraphExtensions { public static DotGraph Directed(this DotGraph graph, bool directed = true) { graph.Directed = directed; return graph; } public static DotGraph Strict(this DotGraph graph, bool strict = true) { graph.Strict = strict; return graph; } } internal static class DotNodeExtensions { public static DotNode WithIdentifier(this DotNode node, string identifier, bool isHtml = false) { node.Identifier = new DotIdentifier(identifier, isHtml); return node; } public static DotNode WithColor(this DotNode node, string color) { node.Color = new DotColorAttribute(color); return node; } public static DotNode WithColor(this DotNode node, DotColor color) { node.Color = new DotColorAttribute(color); return node; } public static DotNode WithFillColor(this DotNode node, string color) { node.FillColor = new DotColorAttribute(color); return node; } public static DotNode WithFillColor(this DotNode node, DotColor color) { node.FillColor = new DotColorAttribute(color); return node; } public static DotNode WithShape(this DotNode node, string shape) { node.Shape = new DotNodeShapeAttribute(shape); return node; } public static DotNode WithShape(this DotNode node, DotNodeShape shape) { node.Shape = new DotNodeShapeAttribute(shape); return node; } public static DotNode WithStyle(this DotNode node, string style) { node.Style = new DotNodeStyleAttribute(style); return node; } public static DotNode WithStyle(this DotNode node, DotNodeStyle style) { node.Style = new DotNodeStyleAttribute(style); return node; } public static DotNode WithWidth(this DotNode node, double width) { node.Width = new DotDoubleAttribute(width); return node; } public static DotNode WithHeight(this DotNode node, double height) { node.Height = new DotDoubleAttribute(height); return node; } public static DotNode WithPenWidth(this DotNode node, double width) { node.PenWidth = new DotDoubleAttribute(width); return node; } public static DotNode WithPos(this DotNode node, string value) { node.Pos = new DotPointAttribute(value); return node; } public static DotNode WithPos(this DotNode node, int x, int y, bool @fixed = false) { node.Pos = new DotPointAttribute(new DotPoint(x, y, @fixed)); return node; } public static DotNode WithPos(this DotNode node, int x, int y, int z, bool @fixed = false) { node.Pos = new DotPointAttribute(new DotPoint(x, y, z, @fixed)); return node; } } internal static class DotSubgraphExtensions { public static DotSubgraph WithColor(this DotSubgraph subgraph, string color) { subgraph.Color = new DotColorAttribute(color); return subgraph; } public static DotSubgraph WithColor(this DotSubgraph subgraph, DotColor color) { subgraph.Color = new DotColorAttribute(color); return subgraph; } public static DotSubgraph WithStyle(this DotSubgraph subgraph, string style) { subgraph.Style = new DotSubgraphStyleAttribute(style); return subgraph; } public static DotSubgraph WithStyle(this DotSubgraph subgraph, DotSubgraphStyle style) { subgraph.Style = new DotSubgraphStyleAttribute(style); return subgraph; } } internal static class EnumExtensions { public static string FlagsToString<T>(this T enumValue) where T : Enum { if (typeof(T).GetCustomAttribute<FlagsAttribute>() == null) { throw new InvalidOperationException($"The type '{typeof(T)}' doesn't have the [Flags] attribute specified."); } return string.Join(",", from T a in Enum.GetValues(typeof(T)) where enumValue.HasFlag(a) select a.ToString().ToLowerInvariant()); } } internal static class StringExtensions { internal static string FormatGraphvizEscapedCharacters(this string value) { return value?.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r\n", "\\n") .Replace("\n", "\\n"); } } } namespace DotNetGraph.Core { internal abstract class DotBaseGraph : DotElement { public DotIdentifier Identifier { get; set; } public DotRankDirAttribute RankDir { get { return GetAttributeOrDefault<DotRankDirAttribute>("rankdir"); } set { SetAttribute("rankdir", value); } } public List<IDotElement> Elements { get; } = new List<IDotElement>(); public DotNode GetNodeByIdentifier(string identifier, bool isHtml = false) { return Elements.Where((IDotElement e) => e is DotNode).Cast<DotNode>().FirstOrDefault((DotNode node) => node.Identifier == new DotIdentifier(identifier, isHtml)); } public abstract override Task CompileAsync(CompilationContext context); protected async Task CompileBodyAsync(CompilationContext context) { await Identifier.CompileAsync(context); await context.WriteLineAsync(" {"); context.IndentationLevel++; await CompileAttributesAsync(context); foreach (IDotElement element in Elements) { await element.CompileAsync(context); } context.IndentationLevel--; await context.WriteIndentationAsync(); await context.WriteLineAsync("}"); } } internal struct DotColor : IEquatable<DotColor> { public byte R; public byte G; public byte B; public byte A; public static DotColor AliceBlue { get; } = new DotColor(240, 248, byte.MaxValue); public static DotColor AntiqueWhite { get; } = new DotColor(250, 235, 215); public static DotColor Aqua { get; } = new DotColor(0, byte.MaxValue, byte.MaxValue); public static DotColor Aquamarine { get; } = new DotColor(127, byte.MaxValue, 212); public static DotColor Azure { get; } = new DotColor(240, byte.MaxValue, byte.MaxValue); public static DotColor Beige { get; } = new DotColor(245, 245, 220); public static DotColor Bisque { get; } = new DotColor(byte.MaxValue, 228, 196); public static DotColor Black { get; } = new DotColor(0, 0, 0); public static DotColor BlanchedAlmond { get; } = new DotColor(byte.MaxValue, 235, 205); public static DotColor Blue { get; } = new DotColor(0, 0, byte.MaxValue); public static DotColor BlueViolet { get; } = new DotColor(138, 43, 226); public static DotColor Brown { get; } = new DotColor(165, 42, 42); public static DotColor Burlywood { get; } = new DotColor(222, 184, 135); public static DotColor CadetBlue { get; } = new DotColor(95, 158, 160); public static DotColor Chartreuse { get; } = new DotColor(127, byte.MaxValue, 0); public static DotColor Chocolate { get; } = new DotColor(210, 105, 30); public static DotColor Coral { get; } = new DotColor(byte.MaxValue, 127, 80); public static DotColor CornflowerBlue { get; } = new DotColor(100, 149, 237); public static DotColor Cornsilk { get; } = new DotColor(byte.MaxValue, 248, 220); public static DotColor Crimson { get; } = new DotColor(220, 20, 60); public static DotColor Cyan { get; } = new DotColor(0, byte.MaxValue, byte.MaxValue); public static DotColor DarkBlue { get; } = new DotColor(0, 0, 139); public static DotColor DarkCyan { get; } = new DotColor(0, 139, 139); public static DotColor DarkGoldenrod { get; } = new DotColor(184, 134, 11); public static DotColor DarkGray { get; } = new DotColor(169, 169, 169); public static DotColor DarkGreen { get; } = new DotColor(0, 100, 0); public static DotColor DarkGrey { get; } = new DotColor(169, 169, 169); public static DotColor DarkKhaki { get; } = new DotColor(189, 183, 107); public static DotColor DarkMagenta { get; } = new DotColor(139, 0, 139); public static DotColor DarkOliveGreen { get; } = new DotColor(85, 107, 47); public static DotColor Darkorange { get; } = new DotColor(byte.MaxValue, 140, 0); public static DotColor DarkOrchid { get; } = new DotColor(153, 50, 204); public static DotColor DarkRed { get; } = new DotColor(139, 0, 0); public static DotColor DarkSalmon { get; } = new DotColor(233, 150, 122); public static DotColor DarkSeaGreen { get; } = new DotColor(143, 188, 143); public static DotColor DarkSlateBlue { get; } = new DotColor(72, 61, 139); public static DotColor DarkSlateGray { get; } = new DotColor(47, 79, 79); public static DotColor DarkSlateGrey { get; } = new DotColor(47, 79, 79); public static DotColor DarkTurquoise { get; } = new DotColor(0, 206, 209); public static DotColor DarkViolet { get; } = new DotColor(148, 0, 211); public static DotColor DeepPink { get; } = new DotColor(byte.MaxValue, 20, 147); public static DotColor DeepSkyBlue { get; } = new DotColor(0, 191, byte.MaxValue); public static DotColor DimGray { get; } = new DotColor(105, 105, 105); public static DotColor DimGrey { get; } = new DotColor(105, 105, 105); public static DotColor DodgerBlue { get; } = new DotColor(30, 144, byte.MaxValue); public static DotColor Firebrick { get; } = new DotColor(178, 34, 34); public static DotColor FloralWhite { get; } = new DotColor(byte.MaxValue, 250, 240); public static DotColor ForestGreen { get; } = new DotColor(34, 139, 34); public static DotColor Fuchsia { get; } = new DotColor(byte.MaxValue, 0, byte.MaxValue); public static DotColor Gainsboro { get; } = new DotColor(220, 220, 220); public static DotColor GhostWhite { get; } = new DotColor(248, 248, byte.MaxValue); public static DotColor Gold { get; } = new DotColor(byte.MaxValue, 215, 0); public static DotColor Goldenrod { get; } = new DotColor(218, 165, 32); public static DotColor Gray { get; } = new DotColor(128, 128, 128); public static DotColor Green { get; } = new DotColor(0, 128, 0); public static DotColor GreenYellow { get; } = new DotColor(173, byte.MaxValue, 47); public static DotColor Grey { get; } = new DotColor(128, 128, 128); public static DotColor Honeydew { get; } = new DotColor(240, byte.MaxValue, 240); public static DotColor HotPink { get; } = new DotColor(byte.MaxValue, 105, 180); public static DotColor IndianRed { get; } = new DotColor(205, 92, 92); public static DotColor Indigo { get; } = new DotColor(75, 0, 130); public static DotColor Ivory { get; } = new DotColor(byte.MaxValue, byte.MaxValue, 240); public static DotColor Khaki { get; } = new DotColor(240, 230, 140); public static DotColor Lavender { get; } = new DotColor(230, 230, 250); public static DotColor LavenderBlush { get; } = new DotColor(byte.MaxValue, 240, 245); public static DotColor LawnGreen { get; } = new DotColor(124, 252, 0); public static DotColor LemonChiffon { get; } = new DotColor(byte.MaxValue, 250, 205); public static DotColor LightBlue { get; } = new DotColor(173, 216, 230); public static DotColor LightCoral { get; } = new DotColor(240, 128, 128); public static DotColor LightCyan { get; } = new DotColor(224, byte.MaxValue, byte.MaxValue); public static DotColor LightGoldenrodYellow { get; } = new DotColor(250, 250, 210); public static DotColor LightGray { get; } = new DotColor(211, 211, 211); public static DotColor LightGrey { get; } = new DotColor(211, 211, 211); public static DotColor LightGreen { get; } = new DotColor(144, 238, 144); public static DotColor LightPink { get; } = new DotColor(byte.MaxValue, 182, 193); public static DotColor LightSalmon { get; } = new DotColor(byte.MaxValue, 160, 122); public static DotColor LightSeaGreen { get; } = new DotColor(32, 178, 170); public static DotColor LightSkyBlue { get; } = new DotColor(135, 206, 250); public static DotColor LightSlateGray { get; } = new DotColor(119, 136, 153); public static DotColor LightSlateGrey { get; } = new DotColor(119, 136, 153); public static DotColor LightSteelBlue { get; } = new DotColor(176, 196, 222); public static DotColor LightYellow { get; } = new DotColor(byte.MaxValue, byte.MaxValue, 224); public static DotColor Lime { get; } = new DotColor(0, byte.MaxValue, 0); public static DotColor LimeGreen { get; } = new DotColor(50, 205, 50); public static DotColor Linen { get; } = new DotColor(250, 240, 230); public static DotColor Magenta { get; } = new DotColor(byte.MaxValue, 0, byte.MaxValue); public static DotColor Maroon { get; } = new DotColor(128, 0, 0); public static DotColor MediumAquamarine { get; } = new DotColor(102, 205, 170); public static DotColor MediumBlue { get; } = new DotColor(0, 0, 205); public static DotColor MediumOrchid { get; } = new DotColor(186, 85, 211); public static DotColor MediumPurple { get; } = new DotColor(147, 112, 219); public static DotColor MediumSeaGreen { get; } = new DotColor(60, 179, 113); public static DotColor MediumSlateBlue { get; } = new DotColor(123, 104, 238); public static DotColor MediumSpringGreen { get; } = new DotColor(0, 250, 154); public static DotColor MediumTurquoise { get; } = new DotColor(72, 209, 204); public static DotColor MediumVioletRed { get; } = new DotColor(199, 21, 133); public static DotColor MidnightBlue { get; } = new DotColor(25, 25, 112); public static DotColor MintCream { get; } = new DotColor(245, byte.MaxValue, 250); public static DotColor MistyRose { get; } = new DotColor(byte.MaxValue, 228, 225); public static DotColor Moccasin { get; } = new DotColor(byte.MaxValue, 228, 181); public static DotColor NavajoWhite { get; } = new DotColor(byte.MaxValue, 222, 173); public static DotColor Navy { get; } = new DotColor(0, 0, 128); public static DotColor OldLace { get; } = new DotColor(253, 245, 230); public static DotColor Olive { get; } = new DotColor(128, 128, 0); public static DotColor OliveDrab { get; } = new DotColor(107, 142, 35); public static DotColor Orange { get; } = new DotColor(byte.MaxValue, 165, 0); public static DotColor OrangeRed { get; } = new DotColor(byte.MaxValue, 69, 0); public static DotColor Orchid { get; } = new DotColor(218, 112, 214); public static DotColor PaleGoldenrod { get; } = new DotColor(238, 232, 170); public static DotColor PaleGreen { get; } = new DotColor(152, 251, 152); public static DotColor PaleTurquoise { get; } = new DotColor(175, 238, 238); public static DotColor PaleVioletRed { get; } = new DotColor(219, 112, 147); public static DotColor PapayaWhip { get; } = new DotColor(byte.MaxValue, 239, 213); public static DotColor PeachPuff { get; } = new DotColor(byte.MaxValue, 218, 185); public static DotColor Peru { get; } = new DotColor(205, 133, 63); public static DotColor Pink { get; } = new DotColor(byte.MaxValue, 192, 203); public static DotColor Plum { get; } = new DotColor(221, 160, 221); public static DotColor PowderBlue { get; } = new DotColor(176, 224, 230); public static DotColor Purple { get; } = new DotColor(128, 0, 128); public static DotColor Red { get; } = new DotColor(byte.MaxValue, 0, 0); public static DotColor RosyBrown { get; } = new DotColor(188, 143, 143); public static DotColor RoyalBlue { get; } = new DotColor(65, 105, 225); public static DotColor SaddleBrown { get; } = new DotColor(139, 69, 19); public static DotColor Salmon { get; } = new DotColor(250, 128, 114); public static DotColor SandyBrown { get; } = new DotColor(244, 164, 96); public static DotColor SeaGreen { get; } = new DotColor(46, 139, 87); public static DotColor SeaShell { get; } = new DotColor(byte.MaxValue, 245, 238); public static DotColor Sienna { get; } = new DotColor(160, 82, 45); public static DotColor Silver { get; } = new DotColor(192, 192, 192); public static DotColor SkyBlue { get; } = new DotColor(135, 206, 235); public static DotColor SlateBlue { get; } = new DotColor(106, 90, 205); public static DotColor SlateGray { get; } = new DotColor(112, 128, 144); public static DotColor SlateGrey { get; } = new DotColor(112, 128, 144); public static DotColor Snow { get; } = new DotColor(byte.MaxValue, 250, 250); public static DotColor SpringGreen { get; } = new DotColor(0, byte.MaxValue, 127); public static DotColor SteelBlue { get; } = new DotColor(70, 130, 180); public static DotColor Tan { get; } = new DotColor(210, 180, 140); public static DotColor Teal { get; } = new DotColor(0, 128, 128); public static DotColor Thistle { get; } = new DotColor(216, 191, 216); public static DotColor Tomato { get; } = new DotColor(byte.MaxValue, 99, 71); public static DotColor Turquoise { get; } = new DotColor(64, 224, 208); public static DotColor Violet { get; } = new DotColor(238, 130, 238); public static DotColor Wheat { get; } = new DotColor(245, 222, 179); public static DotColor White { get; } = new DotColor(byte.MaxValue, byte.MaxValue, byte.MaxValue); public static DotColor WhiteSmoke { get; } = new DotColor(245, 245, 245); public static DotColor Yellow { get; } = new DotColor(byte.MaxValue, byte.MaxValue, 0); public static DotColor YellowGreen { get; } = new DotColor(154, 205, 50); public DotColor(byte r, byte g, byte b, byte a = byte.MaxValue) { R = r; G = g; B = b; A = a; } public bool Equals(DotColor other) { if (R == other.R && G == other.G && B == other.B) { return A == other.A; } return false; } public override bool Equals(object obj) { if (obj is DotColor other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((R.GetHashCode() * 397) ^ G.GetHashCode()) * 397) ^ B.GetHashCode()) * 397) ^ A.GetHashCode(); } public override string ToString() { return $"[DotColor, R:{R}, G:{G}, B:{B}, A:{A}]"; } public string ToHexString() { if (A != byte.MaxValue) { return $"#{R:X2}{G:X2}{B:X2}{A:X2}"; } return $"#{R:X2}{G:X2}{B:X2}"; } } internal class DotEdge : DotElement { public DotIdentifier From { get; set; } public DotIdentifier To { get; set; } public DotColorAttribute Color { get { return GetAttributeOrDefault<DotColorAttribute>("color"); } set { SetAttribute("color", value); } } public DotEdgeStyleAttribute Style { get { return GetAttributeOrDefault<DotEdgeStyleAttribute>("style"); } set { SetAttribute("style", value); } } public DotDoubleAttribute PenWidth { get { return GetAttribute<DotDoubleAttribute>("penwidth"); } set { SetAttribute("penwidth", value); } } public DotEdgeArrowTypeAttribute ArrowHead { get { return GetAttribute<DotEdgeArrowTypeAttribute>("arrowhead"); } set { SetAttribute("arrowhead", value); } } public DotEdgeArrowTypeAttribute ArrowTail { get { return GetAttribute<DotEdgeArrowTypeAttribute>("arrowtail"); } set { SetAttribute("arrowtail", value); } } public DotPointAttribute Pos { get { return GetAttribute<DotPointAttribute>("pos"); } set { SetAttribute("pos", value); } } public override async Task CompileAsync(CompilationContext context) { if ((object)From == null || (object)To == null) { throw new Exception("Can't compile edge with null From and/or To"); } await context.WriteIndentationAsync(); await From.CompileAsync(context); await context.WriteAsync(" " + (context.DirectedGraph ? "->" : "--") + " "); await To.CompileAsync(context); if (Attributes.Any()) { await context.WriteLineAsync(" ["); context.IndentationLevel++; await CompileAttributesAsync(context); context.IndentationLevel--; await context.WriteIndentationAsync(); await context.WriteLineAsync("]"); } else { await context.WriteLineAsync(); } } } internal enum DotEdgeArrowType { Normal, Inv, Dot, InvDot, ODot, InvODot, None, Tee, Empty, InvEmpty, Diamond, ODiamond, EDiamond, Crow, Box, OBox, Open, HalfOpen, Vee } [Flags] internal enum DotEdgeStyle { Solid = 1, Dashed = 2, Dotted = 4, Bold = 8, Tapered = 0x10, Invis = 0x20 } internal abstract class DotElement : IDotElement { protected readonly Dictionary<string, IDotAttribute> Attributes = new Dictionary<string, IDotAttribute>(); public DotLabelAttribute Label { get { return GetAttributeOrDefault<DotLabelAttribute>("label"); } set { SetAttribute("label", value); } } public DotColorAttribute FontColor { get { return GetAttributeOrDefault<DotColorAttribute>("fontcolor"); } set { SetAttribute("fontcolor", value); } } public bool HasAttribute(string name) { return Attributes.ContainsKey(name); } public IDotAttribute GetAttribute(string name) { if (Attributes.TryGetValue(name, out var value)) { return value; } throw new Exception("There is no attribute named '" + name + "'"); } public IDotAttribute GetAttributeOrDefault(string name, IDotAttribute defaultValue = null) { if (Attributes.TryGetValue(name, out var value)) { return value; } return defaultValue; } public T GetAttribute<T>(string name) where T : IDotAttribute { IDotAttribute attribute = GetAttribute(name); if (attribute is T) { return (T)attribute; } throw new Exception($"Attribute with name '{name}' doesn't match the expected type (expected: {typeof(T)}, current: {attribute.GetType()})"); } public T GetAttributeOrDefault<T>(string name, T defaultValue = default(T)) where T : IDotAttribute { if (Attributes.TryGetValue(name, out var value)) { if (value is T) { return (T)value; } throw new Exception($"Attribute with name '{name}' doesn't match the expected type (expected: {typeof(T)}, current: {value.GetType()})"); } return defaultValue; } public bool TryGetAttribute(string name, out IDotAttribute attribute) { if (Attributes.TryGetValue(name, out var value)) { attribute = value; return true; } attribute = null; return false; } public bool TryGetAttribute<T>(string name, out T attribute) where T : IDotAttribute { if (!TryGetAttribute(name, out var attribute2)) { attribute = default(T); return false; } if (attribute2 is T val) { attribute = val; return true; } throw new Exception($"Attribute with name '{name}' doesn't match the expected type (expected: {typeof(T)}, current: {attribute2.GetType()})"); } public void SetAttribute(string name, IDotAttribute value) { if (value == null) { RemoveAttribute(name); } else { Attributes[name] = value; } } public bool RemoveAttribute(string name) { return Attributes.Remove(name); } protected async Task CompileAttributesAsync(CompilationContext context) { foreach (KeyValuePair<string, IDotAttribute> attributePair in Attributes) { await context.WriteIndentationAsync(); await context.WriteAsync("\"" + attributePair.Key + "\"="); await attributePair.Value.CompileAsync(context); await context.WriteLineAsync(); } } public abstract Task CompileAsync(CompilationContext context); } internal class DotGraph : DotBaseGraph { public bool Strict { get; set; } public bool Directed { get; set; } public override async Task CompileAsync(CompilationContext context) { context.DirectedGraph = Directed; await context.WriteIndentationAsync(); if (Strict) { await context.WriteAsync("strict "); } await context.WriteAsync(Directed ? "digraph " : "graph "); await CompileBodyAsync(context); } } internal class DotIdentifier : IDotElement, IEquatable<DotIdentifier> { private static readonly Regex NoQuotesRequiredRegex = new Regex("^([a-zA-Z\\200-\\377_][a-zA-Z\\200-\\3770-9_]*|[-]?(.[0-9]+|[0-9]+(.[0-9]+)?))$"); private static readonly string[] ReservedWords = new string[6] { "graph", "digraph", "subgraph", "strict", "node", "edge" }; public string Value { get; set; } public bool IsHtml { get; set; } public DotIdentifier(string value, bool isHtml = false) { Value = value; IsHtml = isHtml; } public async Task CompileAsync(CompilationContext context) { if (IsHtml) { await context.TextWriter.WriteAsync("<" + Value + ">"); return; } string text = (context.Options.AutomaticEscapedCharactersFormat ? Value.FormatGraphvizEscapedCharacters() : Value); if (RequiresDoubleQuotes(text)) { await context.TextWriter.WriteAsync("\"" + text + "\""); } else { await context.TextWriter.WriteAsync(text ?? ""); } } private static bool RequiresDoubleQuotes(string value) { if (!ReservedWords.Contains(value)) { return !NoQuotesRequiredRegex.IsMatch(value); } return true; } public bool Equals(DotIdentifier other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (Value == other.Value) { return IsHtml == other.IsHtml; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((DotIdentifier)obj); } public override int GetHashCode() { return (((Value != null) ? Value.GetHashCode() : 0) * 397) ^ IsHtml.GetHashCode(); } public static bool operator ==(DotIdentifier identifier1, DotIdentifier identifier2) { return identifier1?.Equals(identifier2) ?? ((object)identifier2 == null); } public static bool operator !=(DotIdentifier identifier1, DotIdentifier identifier2) { return !(identifier1 == identifier2); } } internal class DotNode : DotElement { public DotIdentifier Identifier { get; set; } public DotColorAttribute Color { get { return GetAttributeOrDefault<DotColorAttribute>("color"); } set { SetAttribute("color", value); } } public DotColorAttribute FillColor { get { return GetAttributeOrDefault<DotColorAttribute>("fillcolor"); } set { SetAttribute("fillcolor", value); } } public DotNodeShapeAttribute Shape { get { return GetAttributeOrDefault<DotNodeShapeAttribute>("shape"); } set { SetAttribute("shape", value); } } public DotNodeStyleAttribute Style { get { return GetAttributeOrDefault<DotNodeStyleAttribute>("style"); } set { SetAttribute("style", value); } } public DotDoubleAttribute Width { get { return GetAttribute<DotDoubleAttribute>("width"); } set { SetAttribute("width", value); } } public DotDoubleAttribute Height { get { return GetAttribute<DotDoubleAttribute>("height"); } set { SetAttribute("height", value); } } public DotDoubleAttribute PenWidth { get { return GetAttribute<DotDoubleAttribute>("penwidth"); } set { SetAttribute("penwidth", value); } } public DotPointAttribute Pos { get { return GetAttribute<DotPointAttribute>("pos"); } set { SetAttribute("pos", value); } } public override async Task CompileAsync(CompilationContext context) { await context.WriteIndentationAsync(); await Identifier.CompileAsync(context); if (Attributes.Any()) { await context.WriteLineAsync(" ["); context.IndentationLevel++; await CompileAttributesAsync(context); context.IndentationLevel--; await context.WriteIndentationAsync(); await context.WriteLineAsync("]"); } else { await context.WriteLineAsync(); } } } internal enum DotNodeShape { Box, Polygon, Ellipse, Oval, Circle, Point, Egg, Triangle, PlainText, Plain, Diamond, Trapezium, Parallelogram, House, Pentagon, Hexagon, Septagon, Octagon, DoubleCircle, DoubleOctagon, TripleOctagon, InvTriangle, InvTrapezium, InvHouse, MDiamond, MSquare, MCircle, Rect, Rectangle, Square, Star, None, Underline, Cylinder, Note, Tab, Folder, Box3D, Component, Promoter, CDS, Terminator, UTR, PrimerSite, RestrictionSite, FivePOverHang, ThreePOveHang, NOverHang, Assembly, Signature, Insulator, Ribosite, RNasTab, ProteaseSite, ProteinsTab, RPromoter, RArrow, LArrow, LPromoter } [Flags] internal enum DotNodeStyle { Solid = 1, Dashed = 2, Dotted = 4, Bold = 8, Rounded = 0x10, Diagonals = 0x20, Filled = 0x40, Striped = 0x80, Wedged = 0x100, Invis = 0x200 } internal struct DotPoint { public int X; public int Y; public int? Z; public bool Fixed; public DotPoint(int x, int y, bool @fixed = false) { X = x; Y = y; Z = null; Fixed = @fixed; } public DotPoint(int x, int y, int z, bool @fixed = false) { X = x; Y = y; Z = z; Fixed = @fixed; } public override string ToString() { string text = $"{X},{Y}"; if (Z.HasValue) { text += $",{Z}"; } if (Fixed) { text += "!"; } return text; } } internal enum DotRankDir { TB, BT, LR, RL } internal class DotSubgraph : DotBaseGraph { public DotColorAttribute Color { get { return GetAttributeOrDefault<DotColorAttribute>("color"); } set { SetAttribute("color", value); } } public DotSubgraphStyleAttribute Style { get { return GetAttributeOrDefault<DotSubgraphStyleAttribute>("style"); } set { SetAttribute("style", value); } } public override async Task CompileAsync(CompilationContext context) { await context.WriteIndentationAsync(); await context.WriteAsync("subgraph "); await CompileBodyAsync(context); } } [Flags] internal enum DotSubgraphStyle { Solid = 1, Dashed = 2, Dotted = 4, Bold = 8, Rounded = 0x10, Filled = 0x20, Striped = 0x40, Invis = 0x80 } internal interface IDotElement { Task CompileAsync(CompilationContext context); } } namespace DotNetGraph.Compilation { internal class CompilationContext { public TextWriter TextWriter { get; } public CompilationOptions Options { get; } public int IndentationLevel { get; set; } public bool DirectedGraph { get; set; } public CompilationContext(TextWriter textWriter, CompilationOptions options) { TextWriter = textWriter; Options = options; IndentationLevel = 0; DirectedGraph = false; } public async Task WriteIndentationAsync() { if (Options.Indented) { for (int i = 0; i < IndentationLevel; i++) { await TextWriter.WriteAsync("\t"); } } } public async Task WriteAsync(string value) { await TextWriter.WriteAsync(value); } public async Task WriteLineAsync(string value = null) { await TextWriter.WriteAsync(value); await TextWriter.WriteAsync(Options.Indented ? '\n' : ' '); } } internal class CompilationOptions { public bool AutomaticEscapedCharactersFormat { get; set; } = true; public bool Indented { get; set; } = true; } } namespace DotNetGraph.Attributes { internal class DotAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotAttribute(string value) { Value = value; } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync(Value); } } internal class DotColorAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotColorAttribute(DotColor color) { Value = color.ToHexString(); } public DotColorAttribute(string value) { Value = value; } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotColorAttribute(DotColor value) { return new DotColorAttribute(value); } public static implicit operator DotColorAttribute(string value) { return new DotColorAttribute(value); } } internal class DotDoubleAttribute : IDotAttribute, IDotElement { public double Value { get; set; } public string Format { get; set; } public DotDoubleAttribute(double value, string format = "F2") { Value = value; Format = format; } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync(Value.ToString(Format, NumberFormatInfo.InvariantInfo)); } public static implicit operator DotDoubleAttribute(double value) { return new DotDoubleAttribute(value); } } internal class DotEdgeArrowTypeAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotEdgeArrowTypeAttribute(string value) { Value = value; } public DotEdgeArrowTypeAttribute(DotEdgeArrowType type) { Value = type.ToString().ToLowerInvariant(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotEdgeArrowTypeAttribute(DotEdgeArrowType value) { return new DotEdgeArrowTypeAttribute(value); } public static implicit operator DotEdgeArrowTypeAttribute(string value) { return new DotEdgeArrowTypeAttribute(value); } } internal class DotEdgeStyleAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotEdgeStyleAttribute(string value) { Value = value; } public DotEdgeStyleAttribute(DotEdgeStyle style) { Value = style.FlagsToString(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotEdgeStyleAttribute(DotEdgeStyle value) { return new DotEdgeStyleAttribute(value); } public static implicit operator DotEdgeStyleAttribute(string value) { return new DotEdgeStyleAttribute(value); } } internal class DotLabelAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public bool IsHtml { get; set; } public DotLabelAttribute(string value, bool isHtml = false) { Value = value; IsHtml = isHtml; } public async Task CompileAsync(CompilationContext context) { if (IsHtml) { await context.TextWriter.WriteAsync("<" + Value + ">"); return; } string text = (context.Options.AutomaticEscapedCharactersFormat ? Value.FormatGraphvizEscapedCharacters() : Value); await context.TextWriter.WriteAsync("\"" + text + "\""); } public static implicit operator DotLabelAttribute(string value) { return new DotLabelAttribute(value); } } internal class DotNodeShapeAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotNodeShapeAttribute(string value) { Value = value; } public DotNodeShapeAttribute(DotNodeShape shape) { Value = shape.ToString().ToLowerInvariant(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotNodeShapeAttribute(DotNodeShape value) { return new DotNodeShapeAttribute(value); } public static implicit operator DotNodeShapeAttribute(string value) { return new DotNodeShapeAttribute(value); } } internal class DotNodeStyleAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotNodeStyleAttribute(string value) { Value = value; } public DotNodeStyleAttribute(DotNodeStyle style) { Value = style.FlagsToString(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotNodeStyleAttribute(DotNodeStyle value) { return new DotNodeStyleAttribute(value); } public static implicit operator DotNodeStyleAttribute(string value) { return new DotNodeStyleAttribute(value); } } internal class DotPointAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotPointAttribute(string value) { Value = value; } public DotPointAttribute(DotPoint point) { Value = point.ToString(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync(Value); } public static implicit operator DotPointAttribute(DotPoint value) { return new DotPointAttribute(value); } public static implicit operator DotPointAttribute(string value) { return new DotPointAttribute(value); } } internal class DotRankDirAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotRankDirAttribute(string value) { Value = value; } public DotRankDirAttribute(DotRankDir rankDir) { Value = rankDir.ToString(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotRankDirAttribute(DotRankDir value) { return new DotRankDirAttribute(value); } public static implicit operator DotRankDirAttribute(string value) { return new DotRankDirAttribute(value); } } internal class DotSubgraphStyleAttribute : IDotAttribute, IDotElement { public string Value { get; set; } public DotSubgraphStyleAttribute(string value) { Value = value; } public DotSubgraphStyleAttribute(DotSubgraphStyle style) { Value = style.FlagsToString(); } public async Task CompileAsync(CompilationContext context) { await context.WriteAsync("\"" + Value + "\""); } public static implicit operator DotSubgraphStyleAttribute(DotSubgraphStyle value) { return new DotSubgraphStyleAttribute(value); } public static implicit operator DotSubgraphStyleAttribute(string value) { return new DotSubgraphStyleAttribute(value); } } internal interface IDotAttribute : IDotElement { } }