Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of ExpandRecipeImproved v1.0.2
ExpandRecipe-Improved.dll
Decompiled 2 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using ExpandRecipe; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.UI.Phone.ProductManagerApp; using Il2CppScheduleOne.UI.Tooltips; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonAuthorColor(1, 255, 0, 255)] [assembly: MelonColor(255, 255, 130, 30)] [assembly: AssemblyMetadata("NexusModID", "1405")] [assembly: MelonInfo(typeof(Main), "ExpandRecipe-Improved", "1.0.2", "robbmanes, Patched by: MethodNotAllowed", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ExpandRecipe { public static class GameObjectExtensions { public static void DestroyChildren(this GameObject thisGameObject) { for (int num = thisGameObject.transform.GetChildCount() - 1; num >= 0; num--) { Object.Destroy((Object)(object)((Component)thisGameObject.transform.GetChild(num)).gameObject); } } } public class Main : MelonMod { public struct ExpandedItem { public IngredientQuantity Original; public float EffectiveQuantity; } public string testedVersion = "0.4.5f1"; public static ProductManager productManager; public static ProductManagerApp productManagerApp; public static Instance Log { get; private set; } public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; Log.Msg("Last tested on Schedule I version \"" + testedVersion + "\""); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Main") { try { Il2CppArrayBase<ProductManager> val = Object.FindObjectsOfType<ProductManager>(); if (val.Count > 0) { productManager = val[0]; } else { ((MelonBase)this).LoggerInstance.Warning("No ProductManager found in 'Main' scene."); } } catch (Exception value) { DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(37, 1); defaultInterpolatedStringHandler.AppendLiteral("Failed to find base Product Manager: "); defaultInterpolatedStringHandler.AppendFormatted(value); ((MelonBase)this).LoggerInstance.Error(defaultInterpolatedStringHandler.ToStringAndClear()); } } ((MelonMod)this).OnSceneWasLoaded(buildIndex, sceneName); } public static List<List<ExpandedItem>> GetExpandedRecipes(ProductEntry productEntry) { if ((Object)(object)productManager == (Object)null) { try { Il2CppArrayBase<ProductManager> val = Object.FindObjectsOfType<ProductManager>(); if (val.Count > 0) { productManager = val[0]; } } catch { } } if ((Object)(object)productManager == (Object)null) { Log.Error("ProductManager not found, skipping recursion."); return new List<List<ExpandedItem>>(); } if ((Object)(object)productEntry == (Object)null || (Object)(object)productEntry.Definition == (Object)null) { return new List<List<ExpandedItem>>(); } return GetExpandedRecipesInternal(productEntry.Definition.Recipes, new List<string> { ((BaseItemDefinition)productEntry.Definition).ID }); } private static List<List<ExpandedItem>> GetExpandedRecipesInternal(List<StationRecipe> baseRecipes, List<string> visitedIds, int depth = 0) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Unknown result type (might be due to invalid IL or missing references) if (depth > 15) { return new List<List<ExpandedItem>>(); } List<List<ExpandedItem>> list = new List<List<ExpandedItem>>(); if (baseRecipes.Count > 0) { Enumerator<StationRecipe> enumerator = baseRecipes.GetEnumerator(); while (enumerator.MoveNext()) { StationRecipe current = enumerator.Current; List<IngredientQuantity> list2 = new List<IngredientQuantity>(); Enumerator<IngredientQuantity> enumerator2 = current.Ingredients.GetEnumerator(); while (enumerator2.MoveNext()) { IngredientQuantity current2 = enumerator2.Current; list2 = (((int)((BaseItemDefinition)current2.Item).Category != 0) ? list2.Prepend(current2).ToList() : list2.Append(current2).ToList()); } foreach (IngredientQuantity ingredient in list2) { if ((int)((BaseItemDefinition)ingredient.Item).Category == 0) { Func<ProductDefinition, bool> func = (ProductDefinition x) => ((BaseItemDefinition)x).ID == ((BaseItemDefinition)ingredient.Item).ID; ProductDefinition obj = productManager.AllProducts.Find(Predicate<ProductDefinition>.op_Implicit(func)); if ((Object)(object)obj == (Object)null) { throw new Exception("Could not find base product for \"'" + ((BaseItemDefinition)ingredient.Item).Name + "'\""); } ProductDefinition val = obj; if (val.Recipes.Count <= 0) { List<ExpandedItem> list3 = new List<ExpandedItem>(); list3.Add(new ExpandedItem { Original = ingredient, EffectiveQuantity = ingredient.Quantity }); foreach (IngredientQuantity item in list2) { if (item != ingredient) { list3.Add(new ExpandedItem { Original = item, EffectiveQuantity = item.Quantity }); } } list.Add(list3); continue; } Enumerator<StationRecipe> enumerator5 = val.Recipes.GetEnumerator(); while (enumerator5.MoveNext()) { StationRecipe current4 = enumerator5.Current; if (visitedIds.Contains(((BaseItemDefinition)ingredient.Item).ID)) { List<ExpandedItem> list4 = new List<ExpandedItem>(); list4.Add(new ExpandedItem { Original = ingredient, EffectiveQuantity = ingredient.Quantity }); foreach (IngredientQuantity item2 in list2) { if (item2 != ingredient) { list4.Add(new ExpandedItem { Original = item2, EffectiveQuantity = item2.Quantity }); } } list.Add(list4); continue; } List<string> list5 = new List<string>(); foreach (string visitedId in visitedIds) { list5.Add(visitedId); } list5.Add(((BaseItemDefinition)ingredient.Item).ID); float num = current4.Product.Quantity; float num2 = 1f / num; float num3 = ingredient.Quantity; float num4 = num2 * num3; List<StationRecipe> obj2 = new List<StationRecipe>(); obj2.Add(current4); foreach (List<ExpandedItem> item3 in GetExpandedRecipesInternal(obj2, list5, depth + 1)) { List<ExpandedItem> list6 = new List<ExpandedItem>(); foreach (ExpandedItem item4 in item3) { list6.Add(new ExpandedItem { Original = item4.Original, EffectiveQuantity = item4.EffectiveQuantity * num4 }); } foreach (IngredientQuantity item5 in list2) { if (item5 != ingredient) { list6.Add(new ExpandedItem { Original = item5, EffectiveQuantity = item5.Quantity }); } } list.Add(list6); } } continue; } bool flag = false; foreach (IngredientQuantity item6 in list2) { if ((int)((BaseItemDefinition)item6.Item).Category == 0) { flag = true; } } if (flag || list2.IndexOf(ingredient) != 0) { continue; } List<ExpandedItem> list7 = new List<ExpandedItem>(); foreach (IngredientQuantity item7 in list2) { list7.Add(new ExpandedItem { Original = item7, EffectiveQuantity = item7.Quantity }); } list.Add(list7); } } } return list; } public static GameObject CreateExpandedRecipesTextUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { Transform val = parentGameObject.transform.Find("ExpandedRecipesText"); GameObject gameObject; if ((Object)(object)val == (Object)null) { gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; ((Object)gameObject).name = "ExpandedRecipesText"; gameObject.GetComponent<Text>().text = "Expanded Recipe(s)"; } else { gameObject = ((Component)val).gameObject; } gameObject.gameObject.SetActive(true); return gameObject; } public static GameObject GetOrCreateExpandedRecipesUIGameObject(GameObject parentGameObject) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown Transform val = parentGameObject.transform.Find("ExpandedRecipes"); GameObject gameObject; if ((Object)(object)val == (Object)null) { gameObject = Object.Instantiate<GameObject>(new GameObject(), parentGameObject.transform).gameObject; ((Object)gameObject).name = "ExpandedRecipes"; VerticalLayoutGroup obj = gameObject.gameObject.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f; ((HorizontalOrVerticalLayoutGroup)obj).childScaleHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childScaleWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; } else { gameObject = ((Component)val).gameObject; gameObject.DestroyChildren(); } gameObject.gameObject.SetActive(true); return gameObject; } public static GameObject CreateExpandedRecipeUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.gameObject.SetActive(true); HorizontalLayoutGroup obj = gameObject.gameObject.AddComponent<HorizontalLayoutGroup>(); ((LayoutGroup)obj).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj).childScaleHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childScaleWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; gameObject.DestroyChildren(); return gameObject; } public static GameObject CreateBaseProductUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, List<ExpandedItem> expandedRecipe) { ExpandedItem expandedItem = expandedRecipe.First(); GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = ((BaseItemDefinition)expandedItem.Original.Item).Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = $"{((Object)expandedItem.Original.Item).name} (x{expandedItem.EffectiveQuantity:0.##})"; return gameObject; } public static GameObject CreatePlusUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().preserveAspect = true; return gameObject; } public static GameObject CreateMixUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, ExpandedItem ingredient) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = ((BaseItemDefinition)ingredient.Original.Item).Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = $"{((Object)ingredient.Original.Item).name} (x{ingredient.EffectiveQuantity:0.##})"; return gameObject; } public static GameObject CreateArrowUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { return Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; } public static GameObject CreateOutputUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, ItemQuantity finalProduct) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = ((BaseItemDefinition)finalProduct.Item).Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = ((Object)finalProduct.Item).name; return gameObject; } public static void BuildUIWithRecipe(ProductEntry productEntry, List<List<ExpandedItem>> expandedRecipes, GameObject recipeTextUI, GameObject recipesContainerUI) { if (productEntry.Definition.Recipes == null || productEntry.Definition.Recipes.Count == 0) { return; } ItemQuantity product = productEntry.Definition.Recipes[0].Product; GameObject gameObject = ((Component)recipesContainerUI.transform.Find("Recipe")).gameObject; if ((Object)(object)gameObject == (Object)null) { throw new Exception("Unable to find recipeUI GameObject"); } GameObject val = gameObject; GameObject gameObject2 = ((Component)val.transform.Find("Product")).gameObject; if ((Object)(object)gameObject2 == (Object)null) { throw new Exception("Unable to find productUI GameObject"); } GameObject gameObjectToClone = gameObject2; GameObject gameObject3 = ((Component)val.transform.Find("Plus")).gameObject; if ((Object)(object)gameObject3 == (Object)null) { throw new Exception("Unable to find plusUI GameObject"); } GameObject gameObjectToClone2 = gameObject3; GameObject gameObject4 = ((Component)val.transform.Find("Mixer")).gameObject; if ((Object)(object)gameObject4 == (Object)null) { throw new Exception("Unable to find mixerUI GameObject"); } GameObject gameObjectToClone3 = gameObject4; GameObject gameObject5 = ((Component)val.transform.Find("Arrow")).gameObject; if ((Object)(object)gameObject5 == (Object)null) { throw new Exception("Unable to find arrowUI GameObject"); } GameObject gameObjectToClone4 = gameObject5; GameObject gameObject6 = ((Component)val.transform.Find("Output")).gameObject; if ((Object)(object)gameObject6 == (Object)null) { throw new Exception("Unable to find outputUI GameObject"); } GameObject gameObjectToClone5 = gameObject6; CreateExpandedRecipesTextUIGameObject(recipeTextUI, recipesContainerUI); GameObject orCreateExpandedRecipesUIGameObject = GetOrCreateExpandedRecipesUIGameObject(recipesContainerUI); List<StationRecipe> recipes = productEntry.Definition.Recipes; if (recipes.Count == expandedRecipes.Count) { bool flag = true; for (int i = 0; i < recipes.Count; i++) { StationRecipe val2 = recipes[i]; List<ExpandedItem> list = expandedRecipes[i]; if (val2.Ingredients.Count != list.Count) { flag = false; break; } Enumerator<IngredientQuantity> enumerator = val2.Ingredients.GetEnumerator(); while (enumerator.MoveNext()) { IngredientQuantity current = enumerator.Current; bool flag2 = false; foreach (ExpandedItem item in list) { if (((BaseItemDefinition)item.Original.Item).ID == ((BaseItemDefinition)current.Item).ID) { flag2 = true; break; } } if (!flag2) { flag = false; break; } } if (!flag) { break; } } if (flag) { if ((Object)(object)orCreateExpandedRecipesUIGameObject != (Object)null) { orCreateExpandedRecipesUIGameObject.gameObject.SetActive(false); } CreateExpandedRecipesTextUIGameObject(recipeTextUI, recipesContainerUI).SetActive(false); return; } } foreach (List<ExpandedItem> expandedRecipe in expandedRecipes) { GameObject parentGameObject = CreateExpandedRecipeUIGameObject(val, orCreateExpandedRecipesUIGameObject); CreateBaseProductUIGameObject(gameObjectToClone, parentGameObject, expandedRecipe); float num = 0f; for (int j = 1; j < expandedRecipe.Count; j++) { ExpandedItem expandedItem = expandedRecipe[j]; if (!((Object)(object)expandedItem.Original.Item == (Object)null)) { StorableItemDefinition val3 = ((Il2CppObjectBase)expandedItem.Original.Item).TryCast<StorableItemDefinition>(); if ((Object)(object)val3 != (Object)null) { num += val3.BasePurchasePrice * expandedItem.EffectiveQuantity; } } } float value = productEntry.Definition.MarketValue * (float)product.Quantity - num; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(42, 3); defaultInterpolatedStringHandler.AppendLiteral("Product: "); defaultInterpolatedStringHandler.AppendFormatted(((BaseItemDefinition)product.Item).Name); defaultInterpolatedStringHandler.AppendLiteral(", Cost: "); defaultInterpolatedStringHandler.AppendFormatted(num); defaultInterpolatedStringHandler.AppendLiteral(", Profit: "); defaultInterpolatedStringHandler.AppendFormatted(value); Log.Msg(defaultInterpolatedStringHandler.ToStringAndClear()); string text = "\n-- Breakdown --"; for (int k = 1; k < expandedRecipe.Count; k++) { ExpandedItem expandedItem2 = expandedRecipe[k]; if (!((Object)(object)expandedItem2.Original.Item == (Object)null)) { StorableItemDefinition val4 = ((Il2CppObjectBase)expandedItem2.Original.Item).TryCast<StorableItemDefinition>(); if ((Object)(object)val4 != (Object)null) { float value2 = val4.BasePurchasePrice * expandedItem2.EffectiveQuantity; text += $"\n{((Object)expandedItem2.Original.Item).name}: ${value2:F2} (x{expandedItem2.EffectiveQuantity:0.##} @ ${val4.BasePurchasePrice})"; } } } if (expandedRecipe.Count > 0) { expandedRecipe.Remove(expandedRecipe[0]); } int num2 = expandedRecipe.Count; foreach (ExpandedItem item2 in expandedRecipe) { if (num2 >= 0) { CreatePlusUIGameObject(gameObjectToClone2, parentGameObject); } CreateMixUIGameObject(gameObjectToClone3, parentGameObject, item2); num2--; } CreateArrowUIGameObject(gameObjectToClone4, parentGameObject); Tooltip component = CreateOutputUIGameObject(gameObjectToClone5, parentGameObject, product).GetComponent<Tooltip>(); if ((Object)(object)component != (Object)null) { string text2 = component.text; component.text = text2 + $"\nCost: ${num:F2}\nProfit: ${value:F2}"; component.text += text; } } } } [HarmonyPatch(typeof(ProductManagerApp), "SelectProduct")] public static class ProductManager_SelectProduct_Patch { public static void Prefix(ProductManagerApp __instance, ProductEntry entry) { Main.productManagerApp = __instance; Transform val; Transform val2; try { ProductAppDetailPanel detailPanel = __instance.DetailPanel; val = ((Component)detailPanel).transform.Find("Scroll View/Viewport/Content/RecipesContainer"); if ((Object)(object)val == (Object)null) { MelonLogger.Error("Can't find RecipesContainer object in current scene"); return; } val2 = ((Component)detailPanel).transform.Find("Scroll View/Viewport/Content/Recipes"); if ((Object)(object)val2 == (Object)null) { MelonLogger.Error("Can't find Recipes object in current scene"); return; } Transform val3 = val.Find("ExpandedRecipes"); if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(false); } Transform val4 = val.Find("ExpandedRecipesText"); if ((Object)(object)val4 != (Object)null) { ((Component)val4).gameObject.SetActive(false); } } catch (Exception value) { MelonLogger.Error($"Failed to find Phone UI components: {value}"); return; } List<List<Main.ExpandedItem>> expandedRecipes; try { expandedRecipes = Main.GetExpandedRecipes(entry); foreach (List<Main.ExpandedItem> item in expandedRecipes) { string name = ((BaseItemDefinition)entry.Definition).Name; string text = ""; foreach (Main.ExpandedItem item2 in item) { if (text.Length > 0) { text += " + "; } text += $"{((Object)item2.Original.Item).name}(x{item2.EffectiveQuantity:0.##})"; } MelonLogger.Msg("Expanded Recipe for \"" + name + "\": " + text); } } catch (Exception value2) { MelonLogger.Error($"Failed to get expanded recipe list: {value2}"); return; } try { Main.BuildUIWithRecipe(entry, expandedRecipes, ((Component)val2).gameObject, ((Component)val).gameObject); } catch (Exception value3) { MelonLogger.Error($"Exception raised building UI component: {value3}"); } } } }