Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Schedule Maintenance v1.0.0
Mods/MixingStationMkIII.dll
Decompiled 11 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Building; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Effects; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Levelling; using Il2CppScheduleOne.Misc; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Persistence.Datas; using Il2CppScheduleOne.Persistence.Loaders; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.Tiles; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Items; using Il2CppScheduleOne.UI.Shop; using Il2CppScheduleOne.UI.Stations; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using MixingStationMkIII; using MixingStationMkIII.Model; using MixingStationMkIII.UI; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "Schedule Maintenance", "1.0.0", "beagl", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("MixingStationMkIII")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("MixingStationMkIII")] [assembly: AssemblyTitle("MixingStationMkIII")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [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 MixingStationMkIII { internal sealed class LightSpec { public string Id; public string DisplayName; public string Description; public string ModelResource; public string IconResource; public Func<int> Price; public Func<int> Tier; public Func<string> Rank; public Func<float> Bonus; public LightModel Model; public BuildableItemDefinition Def; public float GrowSpeed => 1f + Math.Max(0f, Bonus()); } internal static class CustomLights { private sealed class Placed { public GrowLight Light; public LightSpec Spec; public MeshRenderer Model; public bool? On; } private const string SourceName = "Full Spectrum"; public static readonly LightSpec[] Specs; private static string _sourceId; private static readonly List<Placed> Lights; private static float _poll; static CustomLights() { Specs = new LightSpec[2] { new LightSpec { Id = "highintensitygrowlight", DisplayName = "High-Intensity Grow Light", Description = "Industrial three-tube grow light. Plants under it grow {0}% faster.", ModelResource = "hilight.bin", IconResource = "hilighticon.png", Price = () => ModConfig.HiLightPrice, Rank = () => ModConfig.HiLightRank, Tier = () => ModConfig.HiLightRankTier, Bonus = () => ModConfig.HiLightGrowBonus }, new LightSpec { Id = "ultraspectrumledarray", DisplayName = "UltraSpectrum LED Array", Description = "Five-bar full-spectrum LED array. Plants under it grow {0}% faster.", ModelResource = "uslight.bin", IconResource = "uslighticon.png", Price = () => ModConfig.UltraLightPrice, Rank = () => ModConfig.UltraLightRank, Tier = () => ModConfig.UltraLightRankTier, Bonus = () => ModConfig.UltraLightGrowBonus } }; Lights = new List<Placed>(); LightSpec[] specs = Specs; foreach (LightSpec lightSpec in specs) { lightSpec.Model = new LightModel(lightSpec.ModelResource, lightSpec.DisplayName); } } public static LightSpec Get(string id) { if (id != null) { return Specs.FirstOrDefault((LightSpec s) => s.Id == id); } return null; } public static LightSpec Get(ItemInstance item) { return Get((item != null) ? ((BaseItemInstance)item).ID : null); } public static void EnsureRegistered() { //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) Registry instance = Singleton<Registry>.Instance; if ((Object)(object)instance == (Object)null) { return; } BuildableItemDefinition val = null; LightSpec[] specs = Specs; foreach (LightSpec lightSpec in specs) { try { if (Registry.ItemExists(lightSpec.Id)) { LightSpec lightSpec2 = lightSpec; if (lightSpec2.Def == null) { ItemDefinition item = Registry.GetItem(lightSpec.Id); lightSpec2.Def = ((item != null) ? ((Il2CppObjectBase)item).TryCast<BuildableItemDefinition>() : null); } continue; } if (val == null) { val = FindSource(instance); } if ((Object)(object)val == (Object)null) { Mod.Log.Warning("Full Spectrum grow light definition not found; " + lightSpec.DisplayName + " not registered."); break; } if ((Object)(object)lightSpec.Def == (Object)null || ((Il2CppObjectBase)lightSpec.Def).WasCollected) { lightSpec.Def = Object.Instantiate<BuildableItemDefinition>(val); ((Object)lightSpec.Def).name = lightSpec.DisplayName.Replace(" ", ""); ((Object)lightSpec.Def).hideFlags = (HideFlags)32; } BuildableItemDefinition def = lightSpec.Def; ((BaseItemDefinition)def).ID = lightSpec.Id; ((BaseItemDefinition)def).Name = lightSpec.DisplayName; ((BaseItemDefinition)def).Description = string.Format(lightSpec.Description, Math.Round(lightSpec.Bonus() * 100f)); Sprite val2 = ModIcons.Load(lightSpec.IconResource, lightSpec.DisplayName + " Icon"); if ((Object)(object)val2 != (Object)null) { ((BaseItemDefinition)def).Icon = val2; } ((StorableItemDefinition)def).BasePurchasePrice = lightSpec.Price(); ((StorableItemDefinition)def).RequiresLevelToPurchase = true; ((StorableItemDefinition)def).RequiredRank = new FullRank { Rank = (ERank)((!Enum.TryParse<ERank>(lightSpec.Rank().Trim().Replace(' ', '_'), ignoreCase: true, out ERank result)) ? 3 : ((int)result)), Tier = lightSpec.Tier() }; instance.AddToRegistry((ItemDefinition)(object)def); Mod.Log.Msg($"Registered item '{lightSpec.Id}' (cloned from '{_sourceId}'), grow speed x{lightSpec.GrowSpeed:0.##}."); } catch (Exception value) { Mod.Log.Error($"Failed to register {lightSpec.DisplayName}: {value}"); } } } private static BuildableItemDefinition FindSource(Registry registry) { BuildableItemDefinition val = null; List<ItemDefinition> allItems = registry.GetAllItems(); for (int i = 0; i < allItems.Count; i++) { ItemDefinition obj = allItems[i]; BuildableItemDefinition val2 = ((obj != null) ? ((Il2CppObjectBase)obj).TryCast<BuildableItemDefinition>() : null); GrowLight val3 = (((Object)(object)((val2 != null) ? val2.BuiltItem : null) != (Object)null) ? ((Component)val2.BuiltItem).GetComponent<GrowLight>() : null); if (!((Object)(object)val3 == (Object)null) && Get(((BaseItemDefinition)val2).ID) == null) { float value = (((Object)(object)val3.usableLightSource != (Object)null) ? val3.usableLightSource.GrowSpeedMultiplier : (-1f)); Mod.Log.Msg($"Grow light '{((BaseItemDefinition)val2).ID}' ({((BaseItemDefinition)val2).Name}): ${((StorableItemDefinition)val2).BasePurchasePrice}, grow speed x{value:0.##}."); if ((Object)(object)val == (Object)null && ((BaseItemDefinition)val2).Name != null && ((BaseItemDefinition)val2).Name.Contains("Full Spectrum", StringComparison.OrdinalIgnoreCase)) { val = val2; } } } _sourceId = ((val != null) ? ((BaseItemDefinition)val).ID : null); return val; } public static void AddShopListings(ShopInterface shop) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown try { if (((shop != null) ? shop.Listings : null) == null) { return; } EnsureRegistered(); if (_sourceId == null) { return; } ShopListing val = null; for (int i = 0; i < shop.Listings.Count; i++) { ShopListing obj = shop.Listings[i]; object obj2; if (obj == null) { obj2 = null; } else { StorableItemDefinition item = obj.Item; obj2 = ((item != null) ? ((BaseItemDefinition)item).ID : null); } if ((string?)obj2 == _sourceId) { val = shop.Listings[i]; } } if (val == null) { return; } LightSpec[] specs = Specs; foreach (LightSpec lightSpec in specs) { if ((Object)(object)lightSpec.Def == (Object)null) { continue; } bool flag = false; for (int k = 0; k < shop.Listings.Count; k++) { bool num = flag; ShopListing obj3 = shop.Listings[k]; object obj4; if (obj3 == null) { obj4 = null; } else { StorableItemDefinition item2 = obj3.Item; obj4 = ((item2 != null) ? ((BaseItemDefinition)item2).ID : null); } flag = num | ((string?)obj4 == lightSpec.Id); } if (!flag) { ShopListing val2 = new ShopListing { name = lightSpec.DisplayName, Item = (StorableItemDefinition)(object)lightSpec.Def, OverridePrice = true, OverriddenPrice = lightSpec.Price(), LimitedStock = false, CanBeDelivered = val.CanBeDelivered, UseIconTint = false, EnforceMinimumGameCreationVersion = false }; shop.Listings.Add(val2); shop.CreateListingUI(val2); Mod.Log.Msg($"Added {lightSpec.DisplayName} (${lightSpec.Price()}, rank {lightSpec.Rank()} {lightSpec.Tier()}) to shop '{shop.ShopName}'."); } } } catch (Exception value) { Mod.Log.Error($"Failed to add the custom light listings: {value}"); } } public static void Attach(GrowLight light, LightSpec spec) { try { if ((Object)(object)light.usableLightSource != (Object)null) { light.usableLightSource.GrowSpeedMultiplier = spec.GrowSpeed; } LightModel.HideVanilla(((Component)light).transform); MeshRenderer val = spec.Model.Build(((Component)light).transform, (Object)(object)light.Light != (Object)null && light.Light.isOn); if (!((Object)(object)val == (Object)null)) { Lights.RemoveAll((Placed p) => (Object)(object)p.Light == (Object)null || ((Il2CppObjectBase)p.Light).WasCollected || ((Il2CppObjectBase)p.Light).Pointer == ((Il2CppObjectBase)light).Pointer); Lights.Add(new Placed { Light = light, Spec = spec, Model = val }); } } catch (Exception value) { Mod.Log.Error($"{spec.DisplayName} setup failed: {value}"); } } public static void ApplyToGhost(GameObject ghost, LightSpec spec) { LightModel.HideVanilla(ghost.transform); spec.Model.Build(ghost.transform); } public static void Update(float dt) { _poll -= dt; if (_poll > 0f) { return; } _poll = 0.2f; for (int num = Lights.Count - 1; num >= 0; num--) { Placed placed = Lights[num]; if ((Object)(object)placed.Light == (Object)null || ((Il2CppObjectBase)placed.Light).WasCollected || (Object)(object)placed.Model == (Object)null || ((Il2CppObjectBase)placed.Model).WasCollected) { Lights.RemoveAt(num); } else { bool flag = (Object)(object)placed.Light.Light != (Object)null && placed.Light.Light.isOn; if (placed.On != flag) { placed.On = flag; placed.Spec.Model.SetGlow(placed.Model, flag); } } } } public static void Clear() { Lights.Clear(); } } internal static class Items { public static string ToJson(ItemInstance item) { return ((SaveData)item.GetItemData()).GetJson(false); } public static ItemInstance FromJson(string json) { if (string.IsNullOrEmpty(json)) { return null; } try { ItemInstance val = ItemDeserializer.LoadItem(json); return (val == null || string.IsNullOrEmpty(((BaseItemInstance)val).ID) || ((BaseItemInstance)val).Quantity <= 0) ? null : val; } catch (Exception ex) { Mod.Log.Warning("Could not restore item from save: " + ex.Message); return null; } } public static T As<T>(Il2CppObjectBase o) where T : Il2CppObjectBase { if (o == null) { return default(T); } return o.TryCast<T>(); } public static bool IsMixableProduct(ItemInstance item) { ProductItemInstance val = Items.As<ProductItemInstance>((Il2CppObjectBase)(object)item); if (val != null) { return (Object)(object)val.AppliedPackaging == (Object)null; } return false; } } internal sealed class Machine { private sealed class SaveDto { public int Version { get; set; } = 3; public string RecipeTarget { get; set; } public string RecipeBase { get; set; } public List<string> RecipeMixers { get; set; } public bool Running { get; set; } public int Stage { get; set; } public int Quantity { get; set; } public int TimedStage { get; set; } = -1; public List<string> ExtraSlots { get; set; } } public MixingStation Station; public string Guid; public readonly List<ItemSlot> ExtraSlots = new List<ItemSlot>(); public RecipeChain Recipe; public MkIIIVisual Visual; public bool Running; public int Stage; public int Quantity; public string Status = ""; public static bool SendingOwnMix; public bool UiOpen; private bool _locked; public int TimedStage = -1; private float _waitForOutput; public int IngredientSlotCount => 1 + ExtraSlots.Count; public bool ExtraSlotsEmpty => ExtraSlots.All((ItemSlot s) => !Has(s)); private int Timed { get { if (TimedStage < 0 || Recipe == null || TimedStage >= Recipe.Stages) { return (Recipe?.Stages ?? 1) - 1; } return TimedStage; } } public int DisplayStage { get { if (!Running || Recipe == null) { return Stage; } int num = MinutesElapsed(); int val = Stage; for (int i = 1; i < Recipe.Stages; i++) { if (num >= ChainMinutes(i)) { val = Math.Max(val, i); } } return Math.Min(val, Recipe.Stages - 1); } } public string StatusText { get { if (!Running || Recipe == null || !(Status ?? "").StartsWith("Mixing stage")) { return Status; } return $"Mixing stage {DisplayStage + 1} / {Recipe.Stages}"; } } public bool IsFirstStage { get { if (Running) { return Stage == 0; } return false; } } public bool IsFinishingStage { get { if (Running && Recipe != null) { return Stage == Timed; } return false; } } public int TimedStageIndex => Timed; public int NewMixesLeft { get { if (!Running || Recipe == null) { return 0; } return Math.Max(1, Recipe.Stages - Stage); } } public bool AwaitingName { get; private set; } public Machine(MixingStation station, string guid) { Station = station; Guid = guid; EnsureExtraSlots(); } public ItemSlot IngredientSlot(int i) { if (i != 0) { if (i - 1 >= ExtraSlots.Count) { return null; } return ExtraSlots[i - 1]; } return Station.MixerSlot; } public static bool Has(ItemSlot s) { if (((s != null) ? s.ItemInstance : null) != null) { return s.Quantity > 0; } return false; } public void EnsureExtraSlots() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown int num = Math.Max(0, ModConfig.MaxMixSteps - 1); while (ExtraSlots.Count < num) { ItemSlot val = new ItemSlot(); val.SetSlotOwner(((Il2CppObjectBase)Station).Cast<IItemSlotOwner>()); ItemSlot mixerSlot = Station.MixerSlot; List<ItemFilter> val2 = ((mixerSlot != null) ? mixerSlot.HardFilters : null); if (val2 != null) { for (int i = 0; i < val2.Count; i++) { val.AddFilter(val2[i]); } } ExtraSlots.Add(val); } List<ItemSlot> inputSlots = Station.InputSlots; if (inputSlots == null) { return; } foreach (ItemSlot extraSlot in ExtraSlots) { if (!inputSlots.Contains(extraSlot)) { inputSlots.Add(extraSlot); } } } public bool EmployeeCanStart(out int quantity) { quantity = 0; if (Running || Recipe == null || Recipe.TargetId == null || Station.CurrentMixOperation != null) { return false; } quantity = GetRunQuantity(Recipe, out var _); return quantity > 0; } public void ApplyRecipeFilters() { if (UiOpen) { return; } if (Recipe == null || Recipe.TargetId == null) { ClearRecipeFilters(); return; } SetFilter(Station.ProductSlot, new string[1] { Recipe.BaseId }); for (int i = 0; i < IngredientSlotCount; i++) { SetFilter(IngredientSlot(i), (i < Recipe.Mixers.Count) ? new string[1] { Recipe.Mixers[i] } : new string[0]); } } public void ClearRecipeFilters() { SetFilter(Station.ProductSlot, null); for (int i = 0; i < IngredientSlotCount; i++) { SetFilter(IngredientSlot(i), null); } } private static void SetFilter(ItemSlot slot, string[] whitelist) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (slot == null) { return; } try { SlotFilter val = (SlotFilter)((slot.PlayerFilter != null) ? ((object)slot.PlayerFilter.Clone()) : ((object)new SlotFilter())); val.ItemIDs = new List<string>(); if (whitelist == null) { val.Type = (EType)0; } else { val.Type = (EType)1; foreach (string text in whitelist) { val.ItemIDs.Add(text); } } slot.SetPlayerFilter(val, false); } catch (Exception ex) { Mod.Log.Warning("MkIII slot filter failed: " + ex.Message); } } public void SetLocks(bool locked) { _locked = locked; Lock(Station.ProductSlot, add: true); for (int i = 0; i < IngredientSlotCount; i++) { Lock(IngredientSlot(i), add: true); } Lock(Station.OutputSlot, add: false); void Lock(ItemSlot s, bool add) { if (s != null) { s.SetIsRemovalLocked(locked); if (add) { s.SetIsAddLocked(locked); } } } } private void Unlocked(Action change) { bool locked = _locked; if (locked) { SetLocks(locked: false); } try { change(); } finally { if (locked) { SetLocks(locked: true); } } } public (string baseId, List<string> mixers) LoadedSequence() { ItemSlot productSlot = Station.ProductSlot; ItemInstance val = ((productSlot != null) ? productSlot.ItemInstance : null); string item = ((val != null && Station.ProductSlot.Quantity > 0 && Items.IsMixableProduct(val)) ? ((BaseItemInstance)val).ID : null); List<string> list = new List<string>(); for (int i = 0; i < IngredientSlotCount; i++) { ItemSlot val2 = IngredientSlot(i); if (!Has(val2)) { break; } list.Add(((BaseItemInstance)val2.ItemInstance).ID); } return (baseId: item, mixers: list); } public int GetRunQuantity(RecipeChain chain, out string problem) { problem = null; if (chain == null || chain.Stages == 0) { problem = "Load a product and an ingredient"; return 0; } if (chain.Stages > ModConfig.MaxMixSteps) { problem = $"Needs more than {ModConfig.MaxMixSteps} mixes"; return 0; } ItemSlot productSlot = Station.ProductSlot; ItemInstance val = ((productSlot != null) ? productSlot.ItemInstance : null); if (val == null || Station.ProductSlot.Quantity <= 0 || ((BaseItemInstance)val).ID != chain.BaseId) { problem = "Insert " + RecipeCatalog.Name(chain.BaseId); return 0; } if (!Items.IsMixableProduct(val)) { problem = "Product must be unpackaged"; return 0; } if (Has(Station.OutputSlot)) { problem = "Take the output first"; return 0; } int num = Math.Min(Station.ProductSlot.Quantity, Math.Max(1, Station.MaxMixQuantity)); for (int i = 0; i < chain.Stages; i++) { ItemSlot val2 = IngredientSlot(i); if (!Has(val2) || ((BaseItemInstance)val2.ItemInstance).ID != chain.Mixers[i]) { problem = $"Mixer {i + 1}: insert {RecipeCatalog.Name(chain.Mixers[i])}"; return 0; } num = Math.Min(num, val2.Quantity); } return num; } public static int ChainMinutes(int stages) { if (stages > 0) { return Math.Max(stages, (int)Math.Round((float)(ModConfig.FirstMixMinutes + ModConfig.ExtraMixMinutes * (stages - 1)) * ModConfig.MixTimeMultiplier)); } return 0; } public int StageMinutes(int stage) { int stages = Recipe?.Stages ?? 1; int timed = Timed; if (stage < timed) { return Math.Max(1, ChainMinutes(stage + 1) - ChainMinutes(stage)); } if (stage > timed) { return 0; } return Math.Max(1, ChainMinutes(stages) - ChainMinutes(stage)); } private static int FirstUnknownStage(RecipeChain chain) { string productId = chain.BaseId; for (int i = 0; i < chain.Mixers.Count; i++) { ProductDefinition val = RecipeCatalog.MixOnce(productId, chain.Mixers[i]); if ((Object)(object)val == (Object)null) { return i; } productId = ((BaseItemDefinition)val).ID; } return chain.Mixers.Count - 1; } public int MinutesElapsed() { if (!Running || Recipe == null) { return 0; } return Math.Max(0, ChainMinutes(Recipe.Stages) - MinutesLeft()); } public int DisplayQuantity(int index, out string itemId) { ItemSlot val = IngredientSlot(index); itemId = (Has(val) ? ((BaseItemInstance)val.ItemInstance).ID : null); int num = (Has(val) ? val.Quantity : 0); if (!Running || Recipe == null || index == 0 || index >= Recipe.Stages) { return num; } if (itemId == null) { itemId = Recipe.Mixers[index]; } if (Stage >= index) { num += Quantity; } if (MinutesElapsed() >= ChainMinutes(index)) { num -= Quantity; } return Math.Max(0, num); } public string Begin(RecipeChain chain, bool isNewMix) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (Running) { return "Already mixing"; } if (Station.CurrentMixOperation != null) { return "Station is busy"; } string problem; int q = GetRunQuantity(chain, out problem); if (q <= 0) { return problem; } if (!isNewMix) { string text = RecipeCatalog.Validate(chain); if (text != null) { return "Recipe invalid: " + text; } } ItemInstance itemInstance = Station.ProductSlot.ItemInstance; QualityItemInstance obj = Items.As<QualityItemInstance>((Il2CppObjectBase)(object)itemInstance); EQuality quality = (EQuality)((obj == null) ? 2 : ((int)obj.Quality)); string iD = ((BaseItemInstance)itemInstance).ID; Unlocked(delegate { Station.ProductSlot.ChangeQuantity(-q, false); IngredientSlot(0).ChangeQuantity(-q, false); }); Recipe = new RecipeChain { TargetId = (isNewMix ? null : chain.TargetId), BaseId = chain.BaseId, Mixers = new List<string>(chain.Mixers) }; ApplyRecipeFilters(); Quantity = q; Stage = 0; TimedStage = (isNewMix ? FirstUnknownStage(Recipe) : (Recipe.Stages - 1)); Running = true; _waitForOutput = 0f; SetLocks(locked: true); SendMix(iD, quality, Recipe.Mixers[0], q); Mod.Log.Msg($"MkIII {Guid}: started {q}x {(isNewMix ? "NEW MIX" : RecipeCatalog.Name(chain.TargetId))} ({chain.Stages} stages, {ChainMinutes(chain.Stages)} min)."); return null; } private void SendMix(string productId, EQuality quality, string mixerId, int quantity) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown Status = $"Mixing stage {Stage + 1} / {Recipe.Stages}"; SendingOwnMix = true; try { Station.SendMixingOperation(new MixOperation(productId, quality, mixerId, quantity), 0); } finally { SendingOwnMix = false; } int mixTimeForCurrentOperation = Station.GetMixTimeForCurrentOperation(); Station.CurrentMixTime = mixTimeForCurrentOperation - StageMinutes(Stage); Mod.Log.Msg($"MkIII {Guid}: stage {Stage + 1}/{Recipe.Stages} {productId} + {mixerId} x{quantity} ({StageMinutes(Stage)} min, station total {mixTimeForCurrentOperation})."); } public int MinutesLeft() { if (!Running || Recipe == null) { return 0; } return ((Station.CurrentMixOperation != null) ? Math.Max(0, Station.GetMixTimeForCurrentOperation() - Station.CurrentMixTime) : 0) + Enumerable.Range(Stage + 1, Math.Max(0, Recipe.Stages - Stage - 1)).Sum((Func<int, int>)StageMinutes); } public void Tick(float dt) { //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0360: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) AwaitingName = false; if (!Running || Recipe == null) { return; } MixOperation currentMixOperation = Station.CurrentMixOperation; if (currentMixOperation != null && !Station.IsMixingDone) { return; } ItemSlot outputSlot = Station.OutputSlot; ItemInstance val = ((outputSlot != null) ? outputSlot.ItemInstance : null); if (!Has(Station.OutputSlot)) { ProductDefinition val2 = default(ProductDefinition); if (currentMixOperation != null && !currentMixOperation.IsOutputKnown(ref val2)) { AwaitingName = true; Status = "NEW MIX discovered! Open the station to name it"; return; } if (_waitForOutput == 0f) { LogStageState("stage ended, no output yet"); } _waitForOutput += dt; Status = "Waiting for stage output"; if (_waitForOutput > 30f) { Running = false; SetLocks(locked: false); Status = "Stopped: stage output missing"; LogStageState("giving up"); Mod.Log.Warning($"MkIII {Guid}: no output after stage {Stage + 1}; stopping."); } return; } _waitForOutput = 0f; int num = Stage + 1; if (num >= Recipe.Stages) { Running = false; SetLocks(locked: false); RecipeChain recipe = Recipe; if (recipe.TargetId == null) { recipe.TargetId = ((BaseItemInstance)val).ID; } ApplyRecipeFilters(); Status = $"Done: {Station.OutputSlot.Quantity}x {((BaseItemInstance)val).Name}"; Mod.Log.Msg($"MkIII {Guid}: finished {Station.OutputSlot.Quantity}x {((BaseItemInstance)val).ID}."); return; } string text = Recipe.Mixers[num]; ItemSlot slot = IngredientSlot(num); int q = Math.Min(Quantity, Station.OutputSlot.Quantity); if (!Has(slot) || ((BaseItemInstance)slot.ItemInstance).ID != text || slot.Quantity < q) { Status = $"Waiting for {q}x {RecipeCatalog.Name(text)} in mixer {num + 1}"; return; } QualityItemInstance obj = Items.As<QualityItemInstance>((Il2CppObjectBase)(object)val); EQuality quality = (EQuality)((obj == null) ? 2 : ((int)obj.Quality)); string iD = ((BaseItemInstance)val).ID; Unlocked(delegate { Station.OutputSlot.ChangeQuantity(-q, false); slot.ChangeQuantity(-q, false); }); Stage = num; Quantity = q; SendMix(iD, quality, text, q); } private void LogStageState(string why) { try { MixOperation currentMixOperation = Station.CurrentMixOperation; ProductDefinition val = default(ProductDefinition); string value = ((currentMixOperation == null) ? "null" : $"{currentMixOperation.ProductID}+{currentMixOperation.IngredientID} x{currentMixOperation.Quantity} known={currentMixOperation.IsOutputKnown(ref val)}"); ProductManager instance = NetworkSingleton<ProductManager>.Instance; NewMixOperation val2 = ((instance != null) ? instance.CurrentMixOperation : null); IEnumerable<string> values; if (Station.ItemSlots != null) { values = Enumerable.Range(0, Station.ItemSlots.Count).Select(delegate(int i) { ItemSlot val3 = Station.ItemSlots[i]; return Has(val3) ? $"[{i}]{((BaseItemInstance)val3.ItemInstance).ID}x{val3.Quantity}" : $"[{i}]-"; }); } else { IEnumerable<string> enumerable = new List<string>(); values = enumerable; } string value2 = string.Join(", ", values); Mod.Log.Msg($"MkIII {Guid} {why}: stage {Stage + 1}/{Recipe?.Stages}, op={value}, done={Station.IsMixingDone}, time={Station.CurrentMixTime}/{((currentMixOperation != null) ? Station.GetMixTimeForCurrentOperation() : 0)}, output={(Has(Station.OutputSlot) ? (((BaseItemInstance)Station.OutputSlot.ItemInstance).ID + "x" + Station.OutputSlot.Quantity) : "-")}, pm.mix={((val2 == null) ? "null" : (val2.ProductID + "+" + val2.IngredientID))} pm.complete={((instance != null) ? new bool?(instance.IsMixComplete) : ((bool?)null))}, slots: {value2}"); } catch (Exception ex) { Mod.Log.Warning("MkIII state log failed: " + ex.Message); } } public string ToJson() { return JsonSerializer.Serialize(new SaveDto { RecipeTarget = Recipe?.TargetId, RecipeBase = Recipe?.BaseId, RecipeMixers = Recipe?.Mixers, Running = Running, Stage = Stage, Quantity = Quantity, TimedStage = TimedStage, ExtraSlots = ExtraSlots.Select((ItemSlot s) => (!Has(s)) ? null : Items.ToJson(s.ItemInstance)).ToList() }); } public void LoadJson(string json) { SaveDto saveDto = JsonSerializer.Deserialize<SaveDto>(json); if (saveDto == null) { return; } Recipe = ((saveDto.RecipeMixers == null || saveDto.RecipeMixers.Count == 0 || string.IsNullOrEmpty(saveDto.RecipeBase)) ? null : new RecipeChain { TargetId = saveDto.RecipeTarget, BaseId = saveDto.RecipeBase, Mixers = saveDto.RecipeMixers }); Stage = Math.Max(0, saveDto.Stage); Quantity = Math.Max(0, saveDto.Quantity); TimedStage = saveDto.TimedStage; Running = saveDto.Running && Recipe != null && Stage < Recipe.Stages; EnsureExtraSlots(); if (saveDto.ExtraSlots != null) { for (int i = 0; i < saveDto.ExtraSlots.Count; i++) { ItemInstance val = Items.FromJson(saveDto.ExtraSlots[i]); if (val != null) { if (i < ExtraSlots.Count) { ExtraSlots[i].SetStoredItem(val, false); continue; } Mod.Log.Warning($"Dropped {((BaseItemInstance)val).Quantity}x {((BaseItemInstance)val).ID} from a MkIII (MaxMixSteps was lowered)."); } } } if (Running) { Status = $"Mixing stage {Stage + 1} / {Recipe.Stages}"; } SetLocks(Running); ApplyRecipeFilters(); } } internal static class MkIIIItem { public const string Id = "mixingstationmk3"; public const string SourceId = "mixingstationmk2"; public const string DisplayName = "Mixing Station MkIII"; private static BuildableItemDefinition _def; private static Sprite _icon; private static readonly string[] IllegalShopMarkers = new string[2] { "phosphorus", "acid" }; public static BuildableItemDefinition Definition => _def; public static bool IsMkIII(ItemInstance item) { if (item != null) { return ((BaseItemInstance)item).ID == "mixingstationmk3"; } return false; } public static void EnsureRegistered() { //IL_013a: Unknown result type (might be due to invalid IL or missing references) try { Registry instance = Singleton<Registry>.Instance; if ((Object)(object)instance == (Object)null) { return; } if (Registry.ItemExists("mixingstationmk3")) { if (_def == null) { ItemDefinition item = Registry.GetItem("mixingstationmk3"); _def = ((item != null) ? ((Il2CppObjectBase)item).TryCast<BuildableItemDefinition>() : null); } } else { if (!Registry.ItemExists("mixingstationmk2")) { return; } ItemDefinition item2 = Registry.GetItem("mixingstationmk2"); BuildableItemDefinition val = ((item2 != null) ? ((Il2CppObjectBase)item2).TryCast<BuildableItemDefinition>() : null); if (!((Object)(object)val == (Object)null)) { if ((Object)(object)_def == (Object)null || ((Il2CppObjectBase)_def).WasCollected) { _def = Object.Instantiate<BuildableItemDefinition>(val); ((Object)_def).name = "MixingStationMkIII"; ((Object)_def).hideFlags = (HideFlags)32; } ((BaseItemDefinition)_def).ID = "mixingstationmk3"; ((BaseItemDefinition)_def).Name = "Mixing Station MkIII"; ((BaseItemDefinition)_def).Description = "Industrial automated mixer. Load a product and up to " + ModConfig.MaxMixSteps + " ingredients in order and it performs every mix stage by itself. Recognises your discovered recipes."; Sprite val2 = LoadIcon(); if ((Object)(object)val2 != (Object)null) { ((BaseItemDefinition)_def).Icon = val2; } ((StorableItemDefinition)_def).BasePurchasePrice = ModConfig.MachinePrice; ((StorableItemDefinition)_def).RequiresLevelToPurchase = true; ((StorableItemDefinition)_def).RequiredRank = ParseRank(); instance.AddToRegistry((ItemDefinition)(object)_def); Mod.Log.Msg("Registered item 'mixingstationmk3' (cloned from 'mixingstationmk2')."); } } } catch (Exception value) { Mod.Log.Error($"Failed to register the MkIII item: {value}"); } } private static FullRank ParseRank() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) ERank result; ERank rank = (ERank)((!Enum.TryParse<ERank>(ModConfig.RequiredRank.Trim().Replace(' ', '_'), ignoreCase: true, out result)) ? 6 : ((int)result)); return new FullRank { Rank = rank, Tier = ModConfig.RequiredRankTier }; } private static Sprite LoadIcon() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_icon != (Object)null && !((Il2CppObjectBase)_icon).WasCollected) { return _icon; } try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("mk3icon.png"); if (stream == null) { return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = "MkIII Icon", hideFlags = (HideFlags)32 }; if (!ImageConversion.LoadImage(val, new Il2CppStructArray<byte>(array))) { return null; } _icon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)_icon).name = "MkIII Icon"; ((Object)_icon).hideFlags = (HideFlags)32; return _icon; } catch (Exception ex) { Mod.Log.Warning("Could not load the MkIII icon: " + ex.Message); return null; } } public static void AddShopListing(ShopInterface shop) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected O, but got Unknown try { if (((shop != null) ? shop.Listings : null) == null) { return; } EnsureRegistered(); if ((Object)(object)_def == (Object)null) { return; } bool flag = false; ShopListing val = null; for (int i = 0; i < shop.Listings.Count; i++) { ShopListing val2 = shop.Listings[i]; if (!((Object)(object)((val2 != null) ? val2.Item : null) == (Object)null)) { if (((BaseItemDefinition)val2.Item).ID == "mixingstationmk3") { return; } if (IllegalShopMarkers.Contains(((BaseItemDefinition)val2.Item).ID)) { flag = true; } if (((BaseItemDefinition)val2.Item).ID == "mixingstationmk2") { val = val2; } } } if (flag) { ShopListing val3 = new ShopListing { name = "Mixing Station MkIII", Item = (StorableItemDefinition)(object)_def, OverridePrice = true, OverriddenPrice = ModConfig.MachinePrice, LimitedStock = false, CanBeDelivered = (val == null || val.CanBeDelivered), UseIconTint = false, EnforceMinimumGameCreationVersion = false }; shop.Listings.Add(val3); shop.CreateListingUI(val3); Mod.Log.Msg($"Added {"Mixing Station MkIII"} (${ModConfig.MachinePrice}, rank {ModConfig.RequiredRank} {ModConfig.RequiredRankTier}) to shop '{shop.ShopName}'."); } } catch (Exception value) { Mod.Log.Error($"Failed to add the MkIII shop listing: {value}"); } } } internal static class Machines { private static readonly Dictionary<IntPtr, Machine> ByStation = new Dictionary<IntPtr, Machine>(); public static readonly Dictionary<string, string> PendingLoad = new Dictionary<string, string>(); public static IEnumerable<Machine> All => ByStation.Values; public static Machine Get(MixingStation station) { if (!((Object)(object)station != (Object)null) || !ByStation.TryGetValue(((Il2CppObjectBase)station).Pointer, out var value)) { return null; } return value; } public static Machine GetOrCreate(MixingStation station, string guid) { Machine machine = Get(station); if (machine != null) { return machine; } machine = new Machine(station, guid); ByStation[((Il2CppObjectBase)station).Pointer] = machine; return machine; } public static void Remove(MixingStation station) { if ((Object)(object)station != (Object)null) { ByStation.Remove(((Il2CppObjectBase)station).Pointer); } } public static void Clear() { ByStation.Clear(); PendingLoad.Clear(); } public static void Prune() { foreach (IntPtr item in (from kv in ByStation where (Object)(object)kv.Value.Station == (Object)null || ((Il2CppObjectBase)kv.Value.Station).WasCollected select kv.Key).ToList()) { ByStation.Remove(item); } } } public sealed class Mod : MelonMod { internal static Instance Log; private bool _inGame; public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; ModConfig.Init(); Log.Msg($"Loaded. MaxMixSteps={ModConfig.MaxMixSteps}, mix time x{ModConfig.MixTimeMultiplier:0.##}, price ${ModConfig.MachinePrice}, debug spawn {(ModConfig.EnableDebugSpawn ? ModConfig.DebugSpawnKey : "off")}."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { _inGame = sceneName == "Main"; if (!_inGame) { Machines.Clear(); CustomLights.Clear(); } MkIIIItem.EnsureRegistered(); CustomLights.EnsureRegistered(); } public override void OnUpdate() { SelfTest.Update(); if (!_inGame) { return; } try { Machines.Prune(); float deltaTime = Time.deltaTime; foreach (Machine item in Machines.All.ToList()) { item.Tick(deltaTime); item.Visual?.Update(deltaTime); } CustomLights.Update(deltaTime); if (ModConfig.EnableDebugSpawn && Input.KeyDown(ModConfig.DebugSpawnKey)) { DebugSpawn(); } } catch (Exception value) { Log.Error($"Update error: {value}"); } } private static void DebugSpawn() { MkIIIItem.EnsureRegistered(); PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; if (!((Object)(object)MkIIIItem.Definition == (Object)null) && !((Object)(object)instance == (Object)null)) { ItemInstance defaultInstance = ((ItemDefinition)MkIIIItem.Definition).GetDefaultInstance(1); if (!instance.CanItemFitInInventory(defaultInstance, 1)) { Log.Warning("Debug spawn: inventory full."); return; } instance.AddItemToInventory(defaultInstance); Log.Msg("Debug spawn: added a Mixing Station MkIII to your inventory."); } } } internal static class Input { private static bool _legacyBroken; public static bool KeyDown(string keyName) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!Enum.TryParse<KeyCode>(keyName, ignoreCase: true, out KeyCode result)) { return false; } if (!_legacyBroken) { try { return Input.GetKeyDown(result); } catch { _legacyBroken = true; } } try { Keyboard current = Keyboard.current; if (current == null) { return false; } if (!Enum.TryParse<Key>(keyName, ignoreCase: true, out Key result2)) { return false; } return ((ButtonControl)current[result2]).wasPressedThisFrame; } catch { return false; } } } internal static class ModConfig { private static MelonPreferences_Category _cat; private static MelonPreferences_Entry<int> _maxMixSteps; private static MelonPreferences_Entry<float> _mixTimeMultiplier; private static MelonPreferences_Entry<int> _firstMixMinutes; private static MelonPreferences_Entry<int> _extraMixMinutes; private static MelonPreferences_Entry<int> _machinePrice; private static MelonPreferences_Entry<string> _requiredRank; private static MelonPreferences_Entry<int> _requiredRankTier; private static MelonPreferences_Entry<bool> _enableDebugSpawn; private static MelonPreferences_Entry<string> _debugSpawnKey; private static MelonPreferences_Entry<int> _hiPrice; private static MelonPreferences_Entry<string> _hiRank; private static MelonPreferences_Entry<int> _hiRankTier; private static MelonPreferences_Entry<float> _hiGrowBonus; private static MelonPreferences_Entry<int> _usPrice; private static MelonPreferences_Entry<string> _usRank; private static MelonPreferences_Entry<int> _usRankTier; private static MelonPreferences_Entry<float> _usGrowBonus; public static int MaxMixSteps => Math.Clamp(_maxMixSteps.Value, 1, 16); public static float MixTimeMultiplier => Math.Max(0.01f, _mixTimeMultiplier.Value); public static int FirstMixMinutes => Math.Max(1, _firstMixMinutes.Value); public static int ExtraMixMinutes => Math.Max(1, _extraMixMinutes.Value); public static int MachinePrice => Math.Max(0, _machinePrice.Value); public static string RequiredRank => _requiredRank.Value; public static int RequiredRankTier => Math.Clamp(_requiredRankTier.Value, 1, 5); public static bool EnableDebugSpawn => _enableDebugSpawn.Value; public static string DebugSpawnKey => _debugSpawnKey.Value; public static int HiLightPrice => Math.Max(0, _hiPrice.Value); public static string HiLightRank => _hiRank.Value; public static int HiLightRankTier => Math.Clamp(_hiRankTier.Value, 1, 5); public static float HiLightGrowBonus => _hiGrowBonus.Value; public static int UltraLightPrice => Math.Max(0, _usPrice.Value); public static string UltraLightRank => _usRank.Value; public static int UltraLightRankTier => Math.Clamp(_usRankTier.Value, 1, 5); public static float UltraLightGrowBonus => _usGrowBonus.Value; public static void Init() { _cat = MelonPreferences.CreateCategory("MixingStationMkIII", "Mixing Station MkIII"); _maxMixSteps = _cat.CreateEntry<int>("MaxMixSteps", 3, "Max mix steps", "Maximum number of sequential mix stages (= ingredient slots) on the MkIII. Takes effect after a game restart.", false, false, (ValueValidator)null, (string)null); _mixTimeMultiplier = _cat.CreateEntry<float>("MixTimeMultiplier", 1f, "Mix time multiplier", "Multiplies the MkIII stage times (FirstMixMinutes / ExtraMixMinutes). 0.5 = twice as fast, 2.0 = half speed.", false, false, (ValueValidator)null, (string)null); _firstMixMinutes = _cat.CreateEntry<int>("FirstMixMinutes", 40, "First mix minutes", "In-game minutes for the first mix stage (1 in-game minute is about 1 real second). Independent of quantity.", false, false, (ValueValidator)null, (string)null); _extraMixMinutes = _cat.CreateEntry<int>("ExtraMixMinutes", 20, "Extra mix minutes", "In-game minutes each additional mix stage adds, so longer chains are rewarded.", false, false, (ValueValidator)null, (string)null); _machinePrice = _cat.CreateEntry<int>("MachinePrice", 8500, "Machine price", "Shop price of the Mixing Station MkIII (the Mk2 costs 2000).", false, false, (ValueValidator)null, (string)null); _requiredRank = _cat.CreateEntry<string>("RequiredRank", "Shot_Caller", "Required rank", "Rank needed to buy it: Street_Rat, Hoodlum, Peddler, Hustler, Bagman, Enforcer, Shot_Caller, Block_Boss, Underlord, Baron, Kingpin.", false, false, (ValueValidator)null, (string)null); _requiredRankTier = _cat.CreateEntry<int>("RequiredRankTier", 1, "Required rank tier", "Tier (1-5) of the required rank.", false, false, (ValueValidator)null, (string)null); _enableDebugSpawn = _cat.CreateEntry<bool>("EnableDebugSpawn", false, "Enable debug spawn", "If true, pressing the debug key in-game puts a Mixing Station MkIII in your inventory for free.", false, false, (ValueValidator)null, (string)null); _debugSpawnKey = _cat.CreateEntry<string>("DebugSpawnKey", "F8", "Debug spawn key", "Key for the debug spawn (UnityEngine KeyCode name).", false, false, (ValueValidator)null, (string)null); _hiPrice = _cat.CreateEntry<int>("HiLightPrice", 600, "High-Intensity light price", "Shop price of the High-Intensity Grow Light.", false, false, (ValueValidator)null, (string)null); _hiRank = _cat.CreateEntry<string>("HiLightRank", "Hustler", "High-Intensity light rank", "Rank needed to buy the High-Intensity Grow Light (same names as RequiredRank).", false, false, (ValueValidator)null, (string)null); _hiRankTier = _cat.CreateEntry<int>("HiLightRankTier", 5, "High-Intensity light rank tier", "Tier (1-5) of that rank.", false, false, (ValueValidator)null, (string)null); _hiGrowBonus = _cat.CreateEntry<float>("HiLightGrowBonus", 0.4f, "High-Intensity light grow bonus", "Extra growth speed under a High-Intensity Grow Light (0.4 = 40% faster than a normal grow light).", false, false, (ValueValidator)null, (string)null); _usPrice = _cat.CreateEntry<int>("UltraLightPrice", 1500, "UltraSpectrum price", "Shop price of the UltraSpectrum LED Array.", false, false, (ValueValidator)null, (string)null); _usRank = _cat.CreateEntry<string>("UltraLightRank", "Bagman", "UltraSpectrum rank", "Rank needed to buy the UltraSpectrum LED Array (same names as RequiredRank).", false, false, (ValueValidator)null, (string)null); _usRankTier = _cat.CreateEntry<int>("UltraLightRankTier", 5, "UltraSpectrum rank tier", "Tier (1-5) of that rank.", false, false, (ValueValidator)null, (string)null); _usGrowBonus = _cat.CreateEntry<float>("UltraLightGrowBonus", 0.5f, "UltraSpectrum grow bonus", "Extra growth speed under an UltraSpectrum LED Array (0.5 = 50% faster than a normal grow light).", false, false, (ValueValidator)null, (string)null); _cat.SaveToFile(false); } } internal static class ModIcons { private static readonly Dictionary<string, Sprite> Cache = new Dictionary<string, Sprite>(); public static Sprite Load(string resource, string name) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (Cache.TryGetValue(resource, out var value) && (Object)(object)value != (Object)null && !((Il2CppObjectBase)value).WasCollected) { return value; } try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource); if (stream == null) { return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = name, hideFlags = (HideFlags)32 }; if (!ImageConversion.LoadImage(val, new Il2CppStructArray<byte>(memoryStream.ToArray()))) { return null; } Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = name; ((Object)val2).hideFlags = (HideFlags)32; Cache[resource] = val2; return val2; } catch (Exception ex) { Mod.Log.Warning("Could not load icon " + resource + ": " + ex.Message); return null; } } } internal static class SaveKeys { public const string AdditionalDataName = "MixingStationMkIII"; } [HarmonyPatch(typeof(ShopInterface), "Start")] internal static class ShopStartPatch { private static void Prefix(ShopInterface __instance) { MkIIIItem.AddShopListing(__instance); CustomLights.AddShopListings(__instance); } } [HarmonyPatch(typeof(MixingStation), "InitializeGridItem")] internal static class InitializeGridItemPatch { private static void Prefix(MixingStation __instance, ref ItemInstance instance, string GUID) { if (GUID != null && Machines.PendingLoad.ContainsKey(GUID) && !MkIIIItem.IsMkIII(instance) && (Object)(object)MkIIIItem.Definition != (Object)null) { instance = ((ItemDefinition)MkIIIItem.Definition).GetDefaultInstance(1); } if (MkIIIItem.IsMkIII(instance)) { try { MkIIIVisual.PrepareShape(((Component)__instance).gameObject); } catch (Exception value) { Mod.Log.Error($"MkIII shape setup failed: {value}"); } } } private static void Postfix(MixingStation __instance, ItemInstance instance, string GUID) { try { if (MkIIIItem.IsMkIII(instance)) { Machine orCreate = Machines.GetOrCreate(__instance, GUID); if (GUID != null && Machines.PendingLoad.Remove(GUID, out var value)) { orCreate.LoadJson(value); Mod.Log.Msg($"Restored MkIII {GUID}: recipe={orCreate.Recipe?.TargetId ?? "none"}, stage {orCreate.Stage + 1}, qty {orCreate.Quantity}, running={orCreate.Running}"); } Machine machine = orCreate; if (machine.Visual == null) { machine.Visual = new MkIIIVisual(orCreate); } orCreate.Visual.Attach(); } } catch (Exception value2) { Mod.Log.Error($"MkIII init failed: {value2}"); } } } [HarmonyPatch(typeof(BuildStart_Grid), "CreateGhostModel")] internal static class GhostPatch { private static void Postfix(BuildableItemDefinition itemDefinition, GridItem __result) { if (!((Object)(object)itemDefinition == (Object)null) && !((Object)(object)__result == (Object)null) && !(((BaseItemDefinition)itemDefinition).ID != "mixingstationmk3")) { MkIIIVisual.ApplyToGhost(((Component)__result).gameObject); } } } [HarmonyPatch(typeof(BuildManager), "ApplyMaterial")] internal static class GhostMaterialPatch { private static void Postfix(GameObject obj, Material mat) { Transform val = (((Object)(object)obj == (Object)null) ? null : (obj.transform.Find("MkIII Model") ?? obj.transform.Find("Custom Light Model"))); if ((Object)(object)val == (Object)null || (Object)(object)mat == (Object)null) { return; } foreach (MeshRenderer componentsInChild in ((Component)val).GetComponentsInChildren<MeshRenderer>(true)) { Il2CppReferenceArray<Material> val2 = new Il2CppReferenceArray<Material>((long)((Il2CppArrayBase<Material>)(object)((Renderer)componentsInChild).sharedMaterials).Length); for (int i = 0; i < ((Il2CppArrayBase<Material>)(object)val2).Length; i++) { ((Il2CppArrayBase<Material>)(object)val2)[i] = mat; } ((Renderer)componentsInChild).sharedMaterials = val2; } } } [HarmonyPatch(typeof(MixingStation), "CanStartMix")] internal static class CanStartMixPatch { private static void Postfix(MixingStation __instance, ref bool __result) { Machine machine = Machines.Get(__instance); if (machine != null) { __result = machine.EmployeeCanStart(out var _); } } } [HarmonyPatch(typeof(MixingStation), "GetMixQuantity")] internal static class GetMixQuantityPatch { private static void Postfix(MixingStation __instance, ref int __result) { Machine machine = Machines.Get(__instance); if (machine != null) { __result = (machine.EmployeeCanStart(out var quantity) ? quantity : 0); } } } [HarmonyPatch(typeof(StartMixingStationBehaviour), "CanCookStart")] internal static class ChemistCanCookPatch { private static void Postfix(StartMixingStationBehaviour __instance, ref bool __result) { Machine machine = Machines.Get(__instance.targetStation); if (machine != null) { __result = machine.EmployeeCanStart(out var _); } } } [HarmonyPatch(typeof(StartMixingStationBehaviour), "StartCook")] internal static class ChemistStartCookPatch { private static bool Prefix(StartMixingStationBehaviour __instance) { Machine machine = Machines.Get(__instance.targetStation); if (machine == null) { return true; } try { string text = machine.Begin(machine.Recipe, isNewMix: false); if (text != null) { Mod.Log.Msg("MkIII " + machine.Guid + ": employee could not start: " + text); } __instance.StopCook(); ((Behaviour)__instance).Deactivate(); } catch (Exception value) { Mod.Log.Error($"MkIII employee start failed: {value}"); } return false; } } [HarmonyPatch(typeof(MixingStationLoader), "Load", new Type[] { typeof(DynamicSaveData) })] internal static class MixingStationLoaderPatch { private static readonly Regex GuidRx = new Regex("\"GUID\"\\s*:\\s*\"([^\"]+)\""); private static void Prefix(DynamicSaveData data) { try { if (((data != null) ? data.AdditionalDatas : null) == null) { return; } for (int i = 0; i < data.AdditionalDatas.Count; i++) { AdditionalData val = data.AdditionalDatas[i]; if (!(((val != null) ? val.Name : null) != "MixingStationMkIII")) { Match match = GuidRx.Match(data.BaseData ?? ""); if (!match.Success) { break; } MkIIIItem.EnsureRegistered(); Machines.PendingLoad[match.Groups[1].Value] = val.Contents; } } } catch (Exception value) { Mod.Log.Error($"MkIII load hook failed: {value}"); } } } [HarmonyPatch(typeof(MixingStation), "GetSaveData")] internal static class GetSaveDataPatch { private static void Postfix(MixingStation __instance, DynamicSaveData __result) { try { Machine machine = Machines.Get(__instance); if (machine != null && __result != null) { __result.BaseData = __result.BaseData?.Replace("mixingstationmk3", "mixingstationmk2"); __result.AddData("MixingStationMkIII", machine.ToJson()); } } catch (Exception value) { Mod.Log.Error($"MkIII save failed: {value}"); } } } [HarmonyPatch(typeof(MixingStation), "Hovered")] internal static class HoveredPatch { private static void Postfix(MixingStation __instance) { if (Machines.Get(__instance) != null && !((Object)(object)__instance.IntObj == (Object)null)) { __instance.IntObj.SetMessage("Use Mixing Station MkIII"); } } } [HarmonyPatch(typeof(MixingStation), "GetManagementName")] internal static class ManagementNamePatch { private static void Postfix(MixingStation __instance, ref string __result) { if (Machines.Get(__instance) != null) { __result = "Mixing Station MkIII"; } } } [HarmonyPatch(typeof(BuildableItem), "CanBePickedUp")] internal static class CanBePickedUpPatch { private static void Postfix(BuildableItem __instance, ref string reason, ref bool __result) { if (__result) { MixingStation val = ((Il2CppObjectBase)__instance).TryCast<MixingStation>(); Machine machine = (((Object)(object)val == (Object)null) ? null : Machines.Get(val)); if (machine != null && (machine.Running || !machine.ExtraSlotsEmpty)) { __result = false; reason = (machine.Running ? "The MkIII is mixing" : "Empty the MkIII ingredient slots first"); } } } } [HarmonyPatch(typeof(MixingStation), "CanBeDestroyed")] internal static class CanBeDestroyedPatch { private static void Postfix(MixingStation __instance, ref string reason, ref bool __result) { Machine machine = Machines.Get(__instance); if (__result && machine != null && (machine.Running || !machine.ExtraSlotsEmpty)) { __result = false; reason = (machine.Running ? "The MkIII is mixing" : "Empty the MkIII ingredient slots first"); } } } [HarmonyPatch(typeof(MixingStation), "Destroy")] internal static class DestroyPatch { private static void Prefix(MixingStation __instance) { Machine machine = Machines.Get(__instance); if (machine != null) { if (!machine.ExtraSlotsEmpty) { Mod.Log.Warning("MkIII " + machine.Guid + " destroyed while holding items."); } machine.Visual?.Detach(); Machines.Remove(__instance); } } } [HarmonyPatch(typeof(MixingStation), "SendMixingOperation")] internal static class BlockVanillaMixPatch { private static bool Prefix(MixingStation __instance) { if (!Machine.SendingOwnMix) { return Machines.Get(__instance) == null; } return true; } } [HarmonyPatch(typeof(MixingStationInterface), "Open")] internal static class InterfaceOpenPatch { private static void Postfix(MixingStationInterface __instance, MixingStation station) { MkIIIInterface.OnOpen(__instance, station); } } [HarmonyPatch(typeof(MixingStationInterface), "Close")] internal static class InterfaceClosePatch { private static void Prefix() { MkIIIInterface.OnClose(); } } [HarmonyPatch] internal static class InterfaceRefreshPatch { private static IEnumerable<MethodBase> TargetMethods() { return new MethodInfo[5] { AccessTools.Method(typeof(MixingStationInterface), "UpdatePreview", (Type[])null, (Type[])null), AccessTools.Method(typeof(MixingStationInterface), "UpdateInstruction", (Type[])null, (Type[])null), AccessTools.Method(typeof(MixingStationInterface), "UpdateBeginButton", (Type[])null, (Type[])null), AccessTools.Method(typeof(MixingStationInterface), "UpdateDisplayMode", (Type[])null, (Type[])null), AccessTools.Method(typeof(MixingStationInterface), "StationContentsChanged", (Type[])null, (Type[])null) }; } private static void Postfix(MixingStationInterface __instance) { if (MkIIIInterface.ActiveFor(__instance.Station)) { MkIIIInterface.Refresh(); } } } [HarmonyPatch(typeof(MixingStationInterface), "Update")] internal static class InterfaceUpdatePatch { private static float _next; private static void Postfix(MixingStationInterface __instance) { if (MkIIIInterface.ActiveFor(__instance.Station) && !(Time.unscaledTime < _next)) { _next = Time.unscaledTime + 0.25f; MkIIIInterface.Refresh(); } } } [HarmonyPatch(typeof(MixingStationInterface), "CheckForUnknownMix")] internal static class InterfaceUnknownMixPatch { private static bool Prefix(MixingStationInterface __instance) { if (!MkIIIInterface.ActiveFor(__instance.Station)) { return true; } MixingStation station = __instance.Station; MixOperation currentMixOperation = station.CurrentMixOperation; ProductDefinition val = default(ProductDefinition); if (currentMixOperation != null && station.IsMixingDone) { return !currentMixOperation.IsOutputKnown(ref val); } return false; } } [HarmonyPatch(typeof(MixingStationInterface), "BeginButtonPressed")] internal static class InterfaceBeginPatch { private static bool Prefix(MixingStationInterface __instance) { if (!MkIIIInterface.ActiveFor(__instance.Station)) { return true; } MkIIIInterface.BeginPressed(); return false; } } [HarmonyPatch(typeof(MixingStation), "MixingStart")] internal static class StageStartSoundPatch { private static bool Prefix(MixingStation __instance) { Machine machine = Machines.Get(__instance); if (machine == null || !machine.Running || machine.IsFirstStage) { return true; } UnityEvent onMixStart = __instance.onMixStart; if (onMixStart != null) { onMixStart.Invoke(); } return false; } } [HarmonyPatch(typeof(MixingStation), "MixingDone")] internal static class StageDoneSoundPatch { private static bool Prefix(MixingStation __instance) { Machine machine = Machines.Get(__instance); if (machine == null || !machine.Running || machine.IsFinishingStage) { return true; } UnityEvent onMixDone = __instance.onMixDone; if (onMixDone != null) { onMixDone.Invoke(); } return false; } } [HarmonyPatch(typeof(MixingStation), "OnTimePass")] internal static class ClockRefreshPatch { private static void Postfix(MixingStation __instance) { Machines.Get(__instance)?.Visual?.UpdateClock(); } } [HarmonyPatch(typeof(MixingStationMk2), "UpdateScreen")] internal static class TabletScreenPatch { private static void Postfix(MixingStationMk2 __instance) { Machine machine = Machines.Get((MixingStation)(object)__instance); if (machine != null && !((Object)(object)__instance.ProgressLabel == (Object)null)) { if (machine.Running && !machine.AwaitingName && ((MixingStation)__instance).CurrentMixOperation != null && !((MixingStation)__instance).IsMixingDone) { ((TMP_Text)__instance.ProgressLabel).text = $"{Math.Max(0, machine.MinutesLeft())} mins remaining"; } else if (machine.AwaitingName || (!machine.Running && ((MixingStation)__instance).CurrentMixOperation != null && ((MixingStation)__instance).IsMixingDone)) { __instance.DisableScreen(); } } } } [HarmonyPatch(typeof(GrowLight), "InitializeProceduralGridItem")] internal static class GrowLightInitPatch { private static void Postfix(GrowLight __instance, ItemInstance instance) { LightSpec lightSpec = CustomLights.Get(instance); if (lightSpec != null) { CustomLights.Attach(__instance, lightSpec); } } } [HarmonyPatch(typeof(BuildStart_ProceduralGrid), "CreateGhostModel")] internal static class GrowLightGhostPatch { private static void Postfix(BuildableItemDefinition itemDefinition, ProceduralGridItem __result) { LightSpec lightSpec = CustomLights.Get((itemDefinition != null) ? ((BaseItemDefinition)itemDefinition).ID : null); if (lightSpec == null || (Object)(object)__result == (Object)null) { return; } try { CustomLights.ApplyToGhost(((Component)__result).gameObject, lightSpec); } catch (Exception value) { Mod.Log.Error($"High-Intensity ghost failed: {value}"); } } } internal sealed class RecipeChain { public string TargetId; public string BaseId; public List<string> Mixers = new List<string>(); public int Stages => Mixers.Count; public string Signature => BaseId + ">" + string.Join(">", Mixers) + "=" + TargetId; public Dictionary<string, int> MixerCounts() { return (from m in Mixers group m by m).ToDictionary((IGrouping<string, string> g) => g.Key, (IGrouping<string, string> g) => g.Count()); } } internal sealed class CatalogEntry { public RecipeChain Chain; public bool TooManyStages; public string Problem; public bool Available { get { if (!TooManyStages) { return Problem == null; } return false; } } } internal static class RecipeCatalog { private const int SearchDepth = 12; public static List<CatalogEntry> Build() { List<CatalogEntry> list = new List<CatalogEntry>(); ProductManager instance = NetworkSingleton<ProductManager>.Instance; if ((Object)(object)instance == (Object)null) { return list; } Dictionary<string, List<(string, string)>> dictionary = new Dictionary<string, List<(string, string)>>(); List<StationRecipe> mixRecipes = instance.mixRecipes; for (int i = 0; i < mixRecipes.Count; i++) { StationRecipe val = mixRecipes[i]; object obj; if (val == null) { obj = null; } else { ItemQuantity product = val.Product; obj = ((product != null) ? product.Item : null); } if ((Object)obj == (Object)null || val.Ingredients == null) { continue; } string text = null; string text2 = null; for (int j = 0; j < val.Ingredients.Count; j++) { IngredientQuantity obj2 = val.Ingredients[j]; ItemDefinition val2 = ((obj2 != null) ? obj2.Item : null); if (!((Object)(object)val2 == (Object)null)) { if ((Object)(object)((Il2CppObjectBase)val2).TryCast<ProductDefinition>() != (Object)null && text == null) { text = ((BaseItemDefinition)val2).ID; } else { text2 = ((BaseItemDefinition)val2).ID; } } } if (text != null && text2 != null) { string iD = ((BaseItemDefinition)val.Product.Item).ID; if (!dictionary.TryGetValue(iD, out var value)) { value = (dictionary[iD] = new List<(string, string)>()); } if (!value.Contains((text, text2))) { value.Add((text, text2)); } } } HashSet<string> hashSet = new HashSet<string>(); List<ProductDefinition> discoveredProducts = ProductManager.DiscoveredProducts; for (int k = 0; k < discoveredProducts.Count; k++) { ProductDefinition val3 = discoveredProducts[k]; if ((Object)(object)val3 == (Object)null || !dictionary.ContainsKey(((BaseItemDefinition)val3).ID) || !hashSet.Add(((BaseItemDefinition)val3).ID)) { continue; } RecipeChain recipeChain = FindChain(((BaseItemDefinition)val3).ID, dictionary); if (recipeChain != null) { CatalogEntry catalogEntry = new CatalogEntry { Chain = recipeChain, TooManyStages = (recipeChain.Stages > ModConfig.MaxMixSteps) }; if (!catalogEntry.TooManyStages) { catalogEntry.Problem = Validate(recipeChain); } list.Add(catalogEntry); } } return (from e in list orderby e.TooManyStages, Name(e.Chain.TargetId) select e).ToList(); } private static RecipeChain FindChain(string target, Dictionary<string, List<(string product, string mixer)>> producers) { Queue<(string, List<string>)> queue = new Queue<(string, List<string>)>(); HashSet<string> hashSet = new HashSet<string> { target }; queue.Enqueue((target, new List<string>())); while (queue.Count > 0) { var (text, list) = queue.Dequeue(); if (!producers.TryGetValue(text, out List<(string, string)> value)) { if (list.Count == 0) { return null; } List<string> list2 = new List<string>(list); list2.Reverse(); return new RecipeChain { TargetId = target, BaseId = text, Mixers = list2 }; } if (list.Count >= 12) { continue; } foreach (var (text2, item) in value) { if (hashSet.Add(text2)) { List<string> item2 = new List<string>(list) { item }; queue.Enqueue((text2, item2)); } } } return null; } public static string Validate(RecipeChain chain) { string text = chain.BaseId; foreach (string mixer in chain.Mixers) { ProductDefinition val = MixOnce(text, mixer); if ((Object)(object)val == (Object)null) { return Name(text) + " + " + Name(mixer) + " gives an undiscovered product"; } text = ((BaseItemDefinition)val).ID; } if (!(text == chain.TargetId)) { return "Chain now produces " + Name(text) + " instead"; } return null; } public static ProductDefinition MixOnce(string productId, string mixerId) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) try { ProductDefinition val = default(ProductDefinition); return new MixOperation(productId, (EQuality)2, mixerId, 1).IsOutputKnown(ref val) ? val : null; } catch (Exception ex) { Mod.Log.Warning($"Mix lookup failed for {productId} + {mixerId}: {ex.Message}"); return null; } } public static string Name(string id) { if (string.IsNullOrEmpty(id)) { return "?"; } ItemDefinition val = (Registry.ItemExists(id) ? Registry.GetItem(id) : null); if (!((Object)(object)val != (Object)null)) { return id; } return ((BaseItemDefinition)val).Name; } } internal static class SelfTest { private enum Step { Idle, Loading, Opening, Selecting, Capturing, ModelShow, ModelCapture, Done } private static Step _step = Step.Idle; private static float _next; private static float _checkFile; private static ShopInterface _shop; private static ListingUI _ui; private static string _itemId = "mixingstationmk3"; private static GameObject _preview; private static string Trigger => Path.Combine(MelonEnvironment.UserDataDirectory, "MkIII_selftest.txt"); private static string Shot => Path.Combine(MelonEnvironment.UserDataDirectory, "MkIII_shop.png"); private static string ModelShot => Path.Combine(MelonEnvironment.UserDataDirectory, "MkIII_model.png"); private static void Start(float now) { string mode = ""; try { mode = File.ReadAllText(Trigger).Trim().ToLowerInvariant(); } catch { } _itemId = CustomLights.Specs.FirstOrDefault((LightSpec s) => mode.Contains(s.Id) || (mode.Contains("hilight") && s.Id.StartsWith("highintensity")))?.Id ?? "mixingstationmk3"; _step = Step.Loading; _shop = null; _ui = null; _next = now + 3f; Mod.Log.Msg("SELFTEST: started (" + _itemId + ")."); } public static void Update() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_0575: Expected O, but got Unknown //IL_058a: Unknown result type (might be due to invalid IL or missing references) //IL_0596: Unknown result type (might be due to invalid IL or missing references) //IL_05a0: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_05b1: Unknown result type (might be due to invalid IL or missing references) //IL_05bb: Unknown result type (might be due to invalid IL or missing references) //IL_05c0: Unknown result type (might be due to invalid IL or missing references) //IL_05d3: Unknown result type (might be due to invalid IL or missing references) //IL_05d8: Unknown result type (might be due to invalid IL or missing references) //IL_05dd: Unknown result type (might be due to invalid IL or missing references) //IL_05e2: Unknown result type (might be due to invalid IL or missing references) //IL_05f6: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) try { float unscaledTime = Time.unscaledTime; if (_step == Step.Idle || _step == Step.Done) { if (unscaledTime < _checkFile) { return; } _checkFile = unscaledTime + 2f; if (!File.Exists(Trigger)) { return; } Start(unscaledTime); } if (unscaledTime < _next) { return; } LoadManager instance = Singleton<LoadManager>.Instance; switch (_step) { case Step.Loading: { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "Menu" && (Object)(object)instance != (Object)null && !instance.IsLoading) { Mod.Log.Msg("SELFTEST: loading last save."); instance.LoadLastSave(); _next = unscaledTime + 10f; break; } if ((Object)(object)instance != (Object)null && instance.IsGameLoaded && instance.TimeSinceGameLoaded > 10f) { _step = Step.Opening; } _next = unscaledTime + 1f; break; } case Step.Opening: { Enumerator<ShopInterface> enumerator = ShopInterface.AllShops.GetEnumerator(); while (enumerator.MoveNext()) { ShopInterface current = enumerator.Current; if (((current != null) ? current.Listings : null) == null) { continue; } bool flag = false; for (int i = 0; i < current.Listings.Count; i++) { bool num = flag; ShopListing obj = current.Listings[i]; object obj2; if (obj == null) { obj2 = null; } else { StorableItemDefinition item = obj.Item; obj2 = ((item != null) ? ((BaseItemDefinition)item).ID : null); } flag = num | ((string?)obj2 == _itemId); } if (flag) { Mod.Log.Msg($"SELFTEST: shop '{current.ShopName}' lists {_itemId} ({current.Listings.Count} listings, {current.listingUI?.Count ?? (-1)} listing buttons)."); if (_shop == null) { _shop = current; } } } if ((Object)(object)_shop == (Object)null) { Finish("no shop lists " + _itemId); break; } _shop.SetIsOpen(true); _step = Step.Selecting; _next = unscaledTime + 1.5f; break; } case Step.Selecting: { int num2 = 0; int num3 = 0; for (int j = 0; j < _shop.listingUI.Count; j++) { ListingUI val2 = _shop.listingUI[j]; if (!((Object)(object)val2 == (Object)null) && ((Component)val2).gameObject.activeSelf) { ShopListing listing = val2.Listing; object obj4; if (listing == null) { obj4 = null; } else { StorableItemDefinition item2 = listing.Item; obj4 = ((item2 != null) ? ((BaseItemDefinition)item2).ID : null); } if ((string?)obj4 == _itemId) { _ui = val2; num2 = num3; } num3++; } } if ((Object)(object)_ui == (Object)null) { Finish("the shop has no visible button for " + _itemId); break; } Instance log2 = Mod.Log; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(55, 6); defaultInterpolatedStringHandler.AppendLiteral("SELFTEST: button "); defaultInterpolatedStringHandler.AppendFormatted(num2 + 1); defaultInterpolatedStringHandler.AppendLiteral("/"); defaultInterpolatedStringHandler.AppendFormatted(num3); defaultInterpolatedStringHandler.AppendLiteral(", name '"); TextMeshProUGUI nameLabel = _ui.NameLabel; defaultInterpolatedStringHandler.AppendFormatted((nameLabel != null) ? ((TMP_Text)nameLabel).text : null); defaultInterpolatedStringHandler.AppendLiteral("', price '"); TextMeshProUGUI priceLabel = _ui.PriceLabel; defaultInterpolatedStringHandler.AppendFormatted((priceLabel != null) ? ((TMP_Text)priceLabel).text : null); defaultInterpolatedStringHandler.AppendLiteral("', "); defaultInterpolatedStringHandler.AppendLiteral("locked="); defaultInterpolatedStringHandler.AppendFormatted((Object)(object)_ui.LockedContainer != (Object)null && _ui.LockedContainer.activeSelf); defaultInterpolatedStringHandler.AppendLiteral(", shown="); defaultInterpolatedStringHandler.AppendFormatted(_ui.Listing.ShouldShow()); defaultInterpolatedStringHandler.AppendLiteral("."); log2.Msg(defaultInterpolatedStringHandler.ToStringAndClear()); if ((Object)(object)_shop.ListingScrollRect != (Object)null && num3 > 1) { _shop.ListingScrollRect.verticalNormalizedPosition = 1f - (float)num2 / (float)(num3 - 1); } _ui.HoverStart(); _step = Step.Capturing; _next = unscaledTime + 1.5f; break; } case Step.Capturing: { ShopInterfaceDetailPanel detailPanel = _shop.DetailPanel; if (detailPanel != null) { detailPanel.Open(_ui); } if (File.Exists(Shot)) { File.Delete(Shot); } ScreenCapture.CaptureScreenshot(Shot); Instance log = Mod.Log; ShopInterfaceDetailPanel detailPanel2 = _shop.DetailPanel; object obj3; if (detailPanel2 == null) { obj3 = null; } else { TextMeshProUGUI descriptionLabel = detailPanel2.DescriptionLabel; obj3 = ((descriptionLabel != null) ? ((TMP_Text)descriptionLabel).text : null); } log.Msg("SELFTEST: detail '" + (string?)obj3 + "'."); if (CustomLights.Get(_itemId) == null) { Finish(null); break; } _step = Step.ModelShow; _next = unscaledTime + 1f; break; } case Step.ModelShow: { _shop.SetIsOpen(false); Camera main = Camera.main; if ((Object)(object)main == (Object)null) { Finish("no camera for the model shot"); break; } _preview = new GameObject("HI Selftest Preview"); Transform transform = _preview.transform; transform.position = ((Component)main).transform.position + ((Component)main).transform.forward * 0.95f + ((Component)main).transform.up * 0.12f; transform.rotation = Quaternion.LookRotation(-((Component)main).transform.forward, Vector3.up) * Quaternion.Euler(-25f, 35f, 0f); MeshRenderer val = CustomLights.Get(_itemId).Model.Build(transform); if ((Object)(object)val == (Object)null) { Finish("model failed to build"); break; } ((Component)val).gameObject.layer = 0; _step = Step.ModelCapture; _next = unscaledTime + 1.5f; break; } case Step.ModelCapture: if (File.Exists(ModelShot)) { File.Delete(ModelShot); } ScreenCapture.CaptureScreenshot(ModelShot); _step = Step.Done; _next = unscaledTime + 1f; Finish(null); break; } } catch (Exception ex) { Finish("error: " + ex.Message); } } private static void Finish(string problem) { _step = Step.Done; try { File.Delete(Trigger); } catch { } if ((Object)(object)_preview != (Object)null && problem != null) { Object.Destroy((Object)(object)_preview); } Mod.Log.Msg((problem == null) ? ("SELFTEST: DONE, screenshot " + Shot) : ("SELFTEST: FAILED, " + problem)); } } } namespace MixingStationMkIII.UI { internal static class MkIIIInterface { private sealed class Preview { public List<CatalogEntry> Candidates = new List<CatalogEntry>(); public CatalogEntry Selected; public RecipeChain Chain; public bool IsNewMix; public string KnownResultId; public bool Filtered; public int LoadedMixers; } private const float SlotGap = 60f; private const float ArrowGap = 99f; private const float PreviewGap = 102f; private static MixingStationInterface _iface; private static Machine _machine; private static bool _built; private static RectTransform _itemSlots; private static RectTransform _plus; private static RectTransform _arrow; private static RectTransform _shade; private static readonly List<ItemSlotUI> ExtraSlotUIs = new List<ItemSlotUI>(); private static readonly List<RectTransform> ExtraPlus = new List<RectTransform>(); private static readonly Dictionary<IntPtr, Image> Ghosts = new Dictionary<IntPtr, Image>(); private static Button _prev; private static Button _next; private static TextMeshProUGUI _title; private static string _vanillaTitle; private static Vector2 _vProduct; private static Vector2 _vPlus; private static Vector2 _vIngredient; private static Vector2 _vArrow; private static Vector2 _vPreview; private static Vector2 _vShade; private static List<CatalogEntry> _catalog = new List<CatalogEntry>(); private static float _catalogAt; private static string _selected; private static IntPtr _namingAskedFor; public static bool ActiveFor(MixingStation station) { if ((Object)(object)_iface != (Object)null && _machine != null) { return (Object)(object)_machine.Station == (Object)(object)station; } return false; } public static void OnOpen(MixingStationInterface iface, MixingStation station) { try { _iface = iface; _machine = Machines.Get(station); Build(iface); if (_machine == null) { RestoreVanilla(); return; } _machine.EnsureExtraSlots(); _machine.UiOpen = true; _machine.ClearRecipeFilters(); _catalog = RecipeCatalog.Build(); _catalogAt = Time.unscaledTime; _selected = _machine.Recipe?.Signature; for (int i = 0; i < ExtraSlotUIs.Count; i++) { ExtraSlotUIs[i].AssignSlot((i < _machine.ExtraSlots.Count) ? _machine.ExtraSlots[i] : null); } AddQuickMoveSlots(); if ((Object)(object)_title != (Object)null) { ((TMP_Text)_title).text = "Mixing Station MkIII"; } ((Component)_prev).gameObject.SetActive(true); ((Component)_next).gameObject.SetActive(true); Refresh(); } catch (Exception value) { Mod.Log.Error($"MkIII screen setup failed: {value}"); } } public static void OnClose() { foreach (ItemSlotUI extraSlotUI in ExtraSlotUIs) { try { extraSlotUI.ClearSlot(); } catch { } } if (_machine != null) { _machine.UiOpen = false; _machine.ApplyRecipeFilters(); } _machine = null; } private static void AddQuickMoveSlots() { ItemUIManager instance = Singleton<ItemUIManager>.Instance; List<ItemSlot> val = ((instance != null) ? instance.SecondarySlots : null); if (val == null) { return; } foreach (ItemSlot extraSlot in _machine.ExtraSlots) { if (!val.Contains(extraSlot)) { val.Add(extraSlot); } } } private static void Build(MixingStationInterface iface) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) if (_built && (Object)(object)_itemSlots != (Object)null && !((Il2CppObjectBase)_itemSlots).WasCollected) { return; } ExtraSlotUIs.Clear(); ExtraPlus.Clear(); Ghosts.Clear(); RectTransform rect = iface.ProductSlotUI.Rect; _itemSlots = ((Il2CppObjectBase)((Transform)rect).parent).Cast<RectTransform>(); Transform obj = ((Transform)_itemSlots).Find("Plus"); _plus = ((obj != null) ? ((Il2CppObjectBase)obj).Cast<RectTransform>() : null); Transform obj2 = ((Transform)_itemSlots).Find("Arrow"); _arrow = ((obj2 != null) ? ((Il2CppObjectBase)obj2).Cast<RectTransform>() : null); Transform obj3 = ((Transform)_itemSlots).Find("Shade"); _shade = ((obj3 != null) ? ((Il2CppObjectBase)obj3).Cast<RectTransform>() : null); _vProduct = rect.anchoredPosition; _vIngredient = iface.IngredientSlotUI.Rect.anchoredPosition; _vPreview = iface.PreviewSlotUI.Rect.anchoredPosition; _vPlus = (((Object)(object)_plus != (Object)null) ? _plus.anchoredPosition : Vector2.zero); _vArrow = (((Object)(object)_arrow != (Object)null) ? _arrow.anchoredPosition : Vector2.zero); _vShade = (((Object)(object)_shade != (Object)null) ? _shade.sizeDelta : Vector2.zero); int num = Math.Max(0, ModConfig.MaxMixSteps - 1); for (int i = 0; i < num; i++) { GameObject obj4 = Object.Instantiate<GameObject>(((Component)iface.IngredientSlotUI).gameObject, (Transform)(object)_itemSlots); ((Object)obj4).name = $"MkIII IngredientSlot {i + 2}"; ItemSlotUI component = obj4.GetComponent<ItemSlotUI>(); component.ClearSlot(); Transform obj5 = obj4.transform.Find("Hint"); if (obj5 != null) { ((Component)obj5).gameObject.SetActive(false); } SetText(obj4.transform.Find("Problem"), ""); ExtraSlotUIs.Add(component); if ((Object)(object)_plus != (Object)null) { RectTransform component2 = Object.Instantiate<GameObject>(((Component)_plus).gameObject, (Transform)(object)_itemSlots).GetComponent<RectTransform>(); ((Object)component2).name = $"MkIII Plus {i + 2}"; ExtraPlus.Add(component2); } } foreach (ItemSlotUI item in AllInputSlotUIs(iface)) { Ghost(item); } Transform parent = ((TMP_Text)iface.InstructionLabel).transform.parent; Transform obj6 = parent.Find("Title"); _title = ((obj6 != null) ? ((Component)obj6).GetComponent<TextMeshProUGUI>() : null); if (_vanillaTitle == null) { TextMeshProUGUI title = _title; _vanillaTitle = ((title != null) ? ((TMP_Text)title).text : null); } RectTransform rectTransform = ((TMP_Text)iface.InstructionLabel).rectTransform; float num2 = rectTransform.sizeDelta.x / 2f + 30f; _prev = ArrowButton(iface, parent, "<", new Vector2(rectTransform.anchoredPosition.x - num2, rectTransform.anchoredPosition.y), delegate { Cycle(-1); }); _next = ArrowButton(iface, parent, ">", new Vector2(rectTransform.anchoredPosition.x + num2, rectTransform.anchoredPosition.y), delegate { Cycle(1); }); _built = true; } private static IEnumerable<ItemSlotUI> AllInputSlotUIs(MixingStationInterface iface) { yield return iface.ProductSlotUI; yield return iface.IngredientSlotUI; foreach (ItemSlotUI extraSlotUI in ExtraSlotUIs) { yield return extraSlotUI; } } private static void Ghost(ItemSlotUI ui) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("MkIII Ghost"); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).SetParent((Transform)(object)ui.Rect, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 0.5f); val2.anchorMax = val3; val2.anchorMin = val3; val2.sizeDelta = new Vector2(56f, 56f); val2.anchoredPosition = Vector2.zero; Transform val4 = ((Transform)ui.Rect).Find("Background"); if ((Object)(object)val4 != (Object)null) { ((Transform)val2).SetSiblingIndex(val4.GetSiblingIndex() + 1); } Image val5 = val.AddComponent<Image>(); ((Graphic)val5).raycastTarget = false; val5.preserveAspect = true; ((Graphic)val5).color = new Color(1f, 1f, 1f, 0.28f); val.SetActive(false); Ghosts[((Il2CppObjectBase)ui).Pointer] = val5; } private static Button ArrowButton(MixingStationInterface iface, Transform parent, string label, Vector2 pos, Action onClick) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown GameObject val = Object.Instantiate<GameObject>(((Component)iface.BeginButton).gameObject, parent); ((Object)val).name = "MkIII Recipe " + label; RectTransform component = val.GetComponent<RectTransform>(); component.anchoredPosition = pos; component.sizeDelta = new Vector2(40f, 40f); foreach (Transform componentsInChild in val.GetComponentsInChildren<Transform>(true)) { if (((Object)componentsInChild).name.StartsWith("EmbeddedInputPrompt")) { ((Component)componentsInChild).gameObject.SetActive(false); } } foreach (TextMeshProUGUI componentsInChild2 in val.GetComponentsInChildren<TextMeshProUGUI>(true)) { ((TMP_Text)componentsInChild2).text = label; } Button component2 = val.GetComponent<Button>(); component2.onClick = new ButtonClickedEvent(); ((UnityEvent)component2.onClick).AddListener(UnityAction.op_Implicit(onClick)); ((Selectable)component2).interactable = true; val.SetActive(false); return component2; } private static void Layout(int ingredientSlots) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) float num = 0f; List<float> list = new List<float> { num }; for (int i = 0; i < ingredientSlots; i++) { num += 60f; list.Add(num); num += 60f; list.Add(num); } num += 99f; list.Add(num); num += 102f; list.Add(num); float num2 = (_vProduct.x + _vPreview.x) / 2f - num / 2f; float y = _vProduct.y; int num3 = 0; _iface.ProductSlotUI.Rect.anchoredPosition = new Vector2(list[num3++] + num2, y); for (int j = 0; j < ingredientSlots; j++) { RectTransform val = ((j == 0) ? _plus : ExtraPlus[j - 1]); RectTransform obj = ((j == 0) ? _iface.IngredientSlotUI.Rect : ExtraSlotUIs[j - 1].Rect); if ((Object)(object)val != (Object)null) { val.anchoredPosition = new Vector2(list[num3] + num2, _vPlus.y); } num3++; obj.anchoredPosition = new Vector2(list[num3++] + num2, y); } if ((Object)(object)_arrow != (Object)null) { _arrow.anchoredPosition = new Vector2(list[num3] + num2, _vArrow.y); } num3++; _iface.PreviewSlotUI.Rect.anchoredPosition = new Vector2(list[num3] + num2, _vPreview.y); if ((Object)(object)_shade != (Object)null) { _shade.sizeDelta = new Vector2(_vShade.x + (num - (_vPreview.x - _vProduct.x)), _vShade.y); } for (int k = 0; k < ExtraSlotUIs.Count; k++) { bool active = k + 1 < ingredientSlots; ((Component)ExtraSlotUIs[k]).gameObject.SetActive(active); if (k < ExtraPlus.Count) { ((Component)ExtraPlus[k]).gameObject.SetActive(active); } } } private static void RestoreVanilla() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (!_built) { return; } _iface.ProductSlotUI.Rect.anchoredPosition = _vProduct; _iface.IngredientSlotUI.Rect.anchoredPosition = _vIngredient; _iface.PreviewSlotUI.Rect.anchoredPosition = _vPreview; if ((Object)(object)_plus != (Object)null) { _plus.anchoredPosition = _vPlus; } if ((Object)(object)_arrow != (Object)null) { _arrow.anchoredPosition = _vArrow; } if ((Object)(object)_shade != (Object)null) { _shade.sizeDelta = _vShade; } foreach (ItemSlotUI extraSlotUI in ExtraSlotUIs) { extraSlotUI.ClearSlot(); ((Component)extraSlotUI).gameObject.SetActive(false); } foreach (RectTransform extraPlu in ExtraPlus) { ((Component)extraPlu).gameObject.SetActive(false); } foreach (Image value in Ghosts.Values) { ((Component)value).gameObject.SetActive(false); } Button prev = _prev; if (prev != null) { ((Component)prev).gameObject.SetActive(false); } Button next = _next; if (next != null) { ((Component)next).gameObject.SetActive(false); } if ((Object)(object)_title != (Object)null && _vanillaTitle != null) { ((TMP_Text)_title).text = _vanillaTitle; } SetText(((Transform)_iface.IngredientSlotUI.Rect).Find("Label"), "Mixer"); SetText(((Transform)_iface.ProductSlotUI.Rect).Find("Label"), "Product"); } private static bool PrefixMatches(List<string> loaded, List<string> recipe) { if (loaded.Count > recipe.Count) { return false; } for (int i = 0; i < loaded.Count; i++) { if (loaded[i] != recipe[i]) { return false; } } return true; } private static Preview ComputePreview() { Machine m = _machine; Preview preview = new Preview(); if (Time.unscaledTime - _catalogAt > 2f) { _catalog = RecipeCatalog.Build(); _catalogAt = Time.unscaledTime; } (string, List<string>) tuple = m.LoadedSequence(); string baseId = tuple.Item1; List<string> mixers = tuple.Item2; preview.LoadedMixers = mixers.Count; preview.Filtered = baseId != null || mixers.Count > 0; preview.Candidates = _catalog.Where((CatalogEntry e) => (baseId == null || e.Chain.BaseId == baseId) && PrefixMatches(mixers, e.Chain.Mixers)).ToList(); if (preview.Candidates.Count > 0) { preview.Selected = preview.Candidates.FirstOrDefault((CatalogEntry e) => e.Chain.Signature == _selected) ?? preview.Candidates.FirstOrDefault((CatalogEntry e) => e.Chain.Signature == m.Recipe?.Signature) ?? preview.Candidates.FirstOrDefault((CatalogEntry e) => e.Available) ?? preview.Candidates[0]; _selected = preview.Selected.Chain.Signature; preview.Chain = preview.Selected.Chain; return preview; } if (!preview.Filtered) { return preview; } preview.Chain = new RecipeChain { BaseId = baseId, Mixers = mixers }; if (baseId == null || mixers.Count == 0) { preview.IsNewMix = true; return preview; } string text = baseId; foreach (string item in mixers) { ProductDefinition val = RecipeCatalog.MixOnce(text, item); if ((Object)(object)val == (Object)null) { preview.IsNewMix = true; return preview; } text = ((BaseItemDefinition)val).ID; } preview.KnownResultId = text; preview.Chain.TargetId = text; return preview; } private static void Cycle(int dir) { if (_machine != null && !_machine.Running) { Preview preview = ComputePreview(); if (preview.Candidates.Count >= 2) { int num = preview.Candidates.IndexOf(preview.Selected); num = ((num + dir) % preview.Candidates.Count + preview.Candidates.Count) % preview.Candidates.Count; _selected = preview.Candidates[num].Chain.Signature; Refresh(); } } } public static void Refresh() { if (_machine == null || (Object)(object)_iface == (Object)null) { return; } try { Machine machine = _machine; MixOperation currentMixOperation = machine.Station.CurrentMixOperation; if (machine.AwaitingName && currentMixOperation != null && ((Il2CppObjectBase)currentMixOperation).Pointer != _namingAskedFor) { NewMixScreen instance = Singleton<NewMixScreen>.Instance; if ((Object)(object)instance != (Object)null && !instance.IsOpen) { _namingAskedFor = ((Il2CppObjectBase)currentMixOperation).Pointer; _iface.CheckForUnknownMix(); } } Preview preview = ComputePreview(); RecipeChain recipeChain = (machine.Running ? machine.Recipe : preview.Chain); bool flag = (machine.Running ? (machine.Recipe?.TargetId == null) : preview.IsNewMix); int maxMixSteps = ModConfig.MaxMixSteps; int num = -1; for (int i = 0; i < machine.IngredientSlotCount; i++) { if (Machine.Has(machine