Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of HolyCasinoStoneExtra v0.1.2
HolyCasinoStoneExtra.dll
Decompiled 7 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Krild_Holy_Casino_Stone.Behaviours; using Microsoft.CodeAnalysis; using Photon.Pun; using REPOLib.Modules; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("HolyCasinoStoneExtra")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.2.0")] [assembly: AssemblyInformationalVersion("0.1.2")] [assembly: AssemblyProduct("HolyCasinoStoneExtra")] [assembly: AssemblyTitle("HolyCasinoStoneExtra")] [assembly: AssemblyVersion("0.1.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 HolyCasinoStoneExtra { [HarmonyPatch(typeof(Krild_Holy_Casino_Stone), "Impact")] internal static class CasinoImpactPatch { [HarmonyPrefix] private static bool ReplaceOriginalImpact(Krild_Holy_Casino_Stone __instance) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return false; } ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>(); if ((Object)(object)component == (Object)null || !CasinoStoneRuntime.HasEstablishedValue(component)) { Plugin.Log.LogWarning((object)"Skipped a Holy Casino Stone roll because its ValuableObject value is not initialized."); return false; } if (CasinoRollScheduler.HasPendingRoll(__instance)) { Plugin.Log.LogDebug((object)"Ignored a duplicate Holy Casino Stone impact while its prior roll was waiting for physical damage."); return false; } Plugin instance = Plugin.Instance; float num = Random.Range(0f, 100f); bool good = num < (float)instance.GoodChance.Value; CasinoRollScheduler.QueueRoll(__instance, component, num, good); return false; } } [HarmonyPatch(typeof(ValuableObject), "DollarValueSetLogic")] internal static class ValuableInitializationTrackingPatch { [HarmonyPostfix] [HarmonyAfter(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(0)] private static void TrackInitializedValue(ValuableObject __instance) { Krild_Holy_Casino_Stone val = CasinoStoneRuntime.FindOriginalBehaviour(__instance); if ((Object)(object)val != (Object)null) { StoneProgressionRuntime.ApplyConfiguredStartingValue(val, __instance); } LevelScalingCompatibility.ObserveKnownValue(__instance); } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "Break")] internal static class StoneBreakRequestDamagePatch { [HarmonyPrefix] [HarmonyBefore(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(800)] private static void ApplyStoneDamageToggle(PhysGrabObjectImpactDetector __instance, ref float valueLost) { if (!Plugin.Instance.StoneTakesDamages.Value && (Object)(object)((Component)__instance).GetComponent<Krild_Holy_Casino_Stone>() != (Object)null) { valueLost = 0f; } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")] internal static class StoneBreakRpcDamagePatch { [HarmonyPrefix] [HarmonyBefore(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(800)] private static void ApplyStoneDamageToggle(PhysGrabObjectImpactDetector __instance, ref float valueLost, out bool __state) { __state = false; if (!Plugin.Instance.StoneTakesDamages.Value && (Object)(object)((Component)__instance).GetComponent<Krild_Holy_Casino_Stone>() != (Object)null) { valueLost = 0f; StoneZeroDamageFeedbackSuppression.Enter(); __state = true; } } [HarmonyPostfix] [HarmonyPriority(0)] private static void EndZeroDamageFeedbackSuppression(bool __state) { if (__state) { StoneZeroDamageFeedbackSuppression.Exit(); } } } [HarmonyPatch(typeof(WorldSpaceUIParent), "ValueLostCreate", new Type[] { typeof(Vector3), typeof(int) })] internal static class StoneZeroDamageValueLostUiPatch { [HarmonyPrefix] private static bool SuppressZeroDamageText(int _value) { if (StoneZeroDamageFeedbackSuppression.Active) { return _value != 0; } return true; } } internal static class StoneZeroDamageFeedbackSuppression { [ThreadStatic] private static int _depth; internal static bool Active => _depth > 0; internal static void Enter() { _depth++; } internal static void Exit() { if (_depth > 0) { _depth--; } } } [HarmonyPatch(typeof(ValuableObject), "DollarValueSetRPC")] internal static class ValueReplacementPatch { internal struct ReplacementState { internal Krild_Holy_Casino_Stone Stone; internal bool WasSet; internal bool WasSurplus; internal float OldOriginal; internal float OldCurrent; } [HarmonyPrefix] private static void CapturePriorValue(ValuableObject __instance, out ReplacementState __state) { Krild_Holy_Casino_Stone stone = CasinoStoneRuntime.FindOriginalBehaviour(__instance); __state = new ReplacementState { Stone = stone, WasSet = CasinoStoneRuntime.HasEstablishedValue(__instance), WasSurplus = ((Object)(object)((Component)__instance).GetComponent<SurplusValuable>() != (Object)null), OldOriginal = CasinoStoneRuntime.GetOriginalValue(__instance), OldCurrent = CasinoStoneRuntime.GetCurrentValue(__instance) }; } [HarmonyPostfix] [HarmonyAfter(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(0)] private static void ReconcileReplacementAndPlayCasinoSound(ValuableObject __instance, float value, ReplacementState __state) { if (__state.WasSet) { LevelScalingCompatibility.OnValueReplaced(__instance, __state.OldOriginal, __state.OldCurrent, CasinoStoneRuntime.GetOriginalValue(__instance), CasinoStoneRuntime.GetCurrentValue(__instance), value, __state.WasSurplus); } else { LevelScalingCompatibility.ObserveKnownValue(__instance); } if ((Object)(object)__state.Stone != (Object)null && __state.WasSet) { float currentValue = CasinoStoneRuntime.GetCurrentValue(__instance); CasinoStoneRuntime.SetOriginalCounter(__state.Stone, currentValue); bool good; bool won = (CasinoRollScheduler.TryConsumeResultSound(__state.Stone, out good) ? good : (currentValue >= __state.OldCurrent)); CasinoStoneRuntime.PlayResultSound(__state.Stone, won); } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")] internal static class ValuableBreakTrackingPatch { [HarmonyPostfix] [HarmonyAfter(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(0)] private static void TrackValueAfterPhysicalDamage(PhysGrabObjectImpactDetector __instance) { ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>(); if ((Object)(object)component != (Object)null) { LevelScalingCompatibility.ObserveKnownValue(component); } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "DestroyObjectRPC")] internal static class ValuableDestructionTrackingPatch { [HarmonyPostfix] [HarmonyAfter(new string[] { "Swaggies.LevelScaling" })] [HarmonyPriority(0)] private static void ForgetDestroyedValue(PhysGrabObjectImpactDetector __instance) { ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>(); Krild_Holy_Casino_Stone component2 = ((Component)__instance).GetComponent<Krild_Holy_Casino_Stone>(); if ((Object)(object)component != (Object)null) { LevelScalingCompatibility.ForgetValue(component); } if ((Object)(object)component2 != (Object)null) { CasinoRollScheduler.Forget(component2); StoneProgressionRuntime.Forget(component2); } } } internal static class CasinoRollScheduler { private sealed class PendingRoll { internal Krild_Holy_Casino_Stone Stone; internal ValuableObject Valuable; internal float Roll; internal bool Good; } private static readonly Dictionary<int, PendingRoll> PendingRolls = new Dictionary<int, PendingRoll>(); private static readonly Dictionary<int, bool> PendingResultSounds = new Dictionary<int, bool>(); internal static bool HasPendingRoll(Krild_Holy_Casino_Stone stone) { if ((Object)(object)stone != (Object)null) { return PendingRolls.ContainsKey(((Object)stone).GetInstanceID()); } return false; } internal static void QueueRoll(Krild_Holy_Casino_Stone stone, ValuableObject valuable, float roll, bool good) { int instanceID = ((Object)stone).GetInstanceID(); PendingRoll pendingRoll = new PendingRoll { Stone = stone, Valuable = valuable, Roll = roll, Good = good }; PendingRolls[instanceID] = pendingRoll; ((MonoBehaviour)Plugin.Instance).StartCoroutine(ApplyAfterPhysicalDamage(instanceID, pendingRoll)); } internal static void Clear() { PendingRolls.Clear(); PendingResultSounds.Clear(); } internal static bool TryConsumeResultSound(Krild_Holy_Casino_Stone stone, out bool good) { good = false; if ((Object)(object)stone == (Object)null) { return false; } int instanceID = ((Object)stone).GetInstanceID(); if (!PendingResultSounds.TryGetValue(instanceID, out good)) { return false; } PendingResultSounds.Remove(instanceID); return true; } internal static void Forget(Krild_Holy_Casino_Stone stone) { if (!((Object)(object)stone == (Object)null)) { int instanceID = ((Object)stone).GetInstanceID(); PendingRolls.Remove(instanceID); PendingResultSounds.Remove(instanceID); } } private static IEnumerator ApplyAfterPhysicalDamage(int key, PendingRoll pending) { yield return null; if (!PendingRolls.TryGetValue(key, out var value) || value != pending) { yield break; } PendingRolls.Remove(key); if ((Object)(object)pending.Stone == (Object)null || (Object)(object)pending.Valuable == (Object)null || !SemiFunc.IsMasterClientOrSingleplayer() || !CasinoStoneRuntime.HasEstablishedValue(pending.Valuable)) { yield break; } float currentValue = CasinoStoneRuntime.GetCurrentValue(pending.Valuable); if (!StoneProgressionRuntime.TryCalculateOutcome(pending.Stone, currentValue, pending.Good, out var newValue, out var outcome, out var setValue)) { Plugin.Log.LogDebug((object)$"Holy Casino Stone rolled {pending.Roll:0.00}: {outcome}."); yield break; } if (!setValue) { Plugin.Log.LogDebug((object)$"Holy Casino Stone rolled {pending.Roll:0.00}: {outcome}; value unchanged at {currentValue:0}."); yield break; } newValue = CasinoStoneRuntime.SanitizeDollarValue(newValue, currentValue); PendingResultSounds[key] = pending.Good; if (!CasinoStoneRuntime.SetNetworkedDollarValue(pending.Valuable, newValue)) { PendingResultSounds.Remove(key); yield break; } CasinoStoneRuntime.SetOriginalCounter(pending.Stone, newValue); Plugin.Log.LogDebug((object)$"Holy Casino Stone rolled {pending.Roll:0.00}: {outcome}; {currentValue:0} -> {newValue:0} after physical damage."); } } internal static class CasinoStoneRuntime { private const float MaximumSafeDollarValue = 2E+09f; private static readonly FieldInfo DollarValueOriginalField = AccessTools.Field(typeof(ValuableObject), "dollarValueOriginal"); private static readonly FieldInfo DollarValueCurrentField = AccessTools.Field(typeof(ValuableObject), "dollarValueCurrent"); private static readonly FieldInfo DollarValueSetField = AccessTools.Field(typeof(ValuableObject), "dollarValueSet"); private static readonly FieldInfo OriginalCounterField = AccessTools.Field(typeof(Krild_Holy_Casino_Stone), "valueDollar"); private static readonly FieldInfo WinSoundField = AccessTools.Field(typeof(Krild_Holy_Casino_Stone), "winSound"); private static readonly FieldInfo LoseSoundField = AccessTools.Field(typeof(Krild_Holy_Casino_Stone), "noSound"); internal static bool Validate(out string error) { if (AccessTools.Method(typeof(Krild_Holy_Casino_Stone), "Impact", (Type[])null, (Type[])null) == null) { error = "the original Holy Casino Stone Impact method was not found."; return false; } if (AccessTools.Method(typeof(ValuableObject), "DollarValueSetRPC", new Type[2] { typeof(float), typeof(PhotonMessageInfo) }, (Type[])null) == null) { error = "ValuableObject.DollarValueSetRPC(float, PhotonMessageInfo) was not found."; return false; } if (DollarValueOriginalField == null || DollarValueOriginalField.FieldType != typeof(float)) { error = "ValuableObject.dollarValueOriginal was not found as a float."; return false; } if (DollarValueCurrentField == null || DollarValueCurrentField.FieldType != typeof(float)) { error = "ValuableObject.dollarValueCurrent was not found as a float."; return false; } if (DollarValueSetField == null || DollarValueSetField.FieldType != typeof(bool)) { error = "ValuableObject.dollarValueSet was not found as a bool."; return false; } if (OriginalCounterField == null || OriginalCounterField.FieldType != typeof(float)) { error = "the original Holy Casino Stone valueDollar field was not found as a float."; return false; } if (WinSoundField == null || LoseSoundField == null) { error = "the original Holy Casino Stone sound fields were not found."; return false; } error = null; return true; } internal static float GetOriginalValue(ValuableObject valuable) { return (float)DollarValueOriginalField.GetValue(valuable); } internal static float GetCurrentValue(ValuableObject valuable) { return (float)DollarValueCurrentField.GetValue(valuable); } internal static bool HasEstablishedValue(ValuableObject valuable) { return (bool)DollarValueSetField.GetValue(valuable); } internal static void SetLocalDollarValue(ValuableObject valuable, float value) { DollarValueOriginalField.SetValue(valuable, value); DollarValueCurrentField.SetValue(valuable, value); DollarValueSetField.SetValue(valuable, true); } internal static void SetOriginalCounter(Krild_Holy_Casino_Stone stone, float value) { OriginalCounterField.SetValue(stone, value); } internal static float SanitizeDollarValue(float value, float fallback) { if (float.IsNaN(value) || float.IsInfinity(value)) { value = fallback; } return Mathf.Round(Mathf.Clamp(value, 0f, 2E+09f)); } internal static bool SetNetworkedDollarValue(ValuableObject valuable, float value) { //IL_0051: 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) if (SemiFunc.IsMultiplayer()) { PhotonView component = ((Component)valuable).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null || component.ViewID == 0) { Plugin.Log.LogWarning((object)"Skipped a Holy Casino Stone value change because its PhotonView is not ready."); return false; } component.RPC("DollarValueSetRPC", (RpcTarget)0, new object[1] { value }); return true; } valuable.DollarValueSetRPC(value, default(PhotonMessageInfo)); return true; } internal static Krild_Holy_Casino_Stone FindOriginalBehaviour(ValuableObject valuable) { if (!((Object)(object)valuable == (Object)null)) { return ((Component)valuable).GetComponent<Krild_Holy_Casino_Stone>(); } return null; } internal static void PlayResultSound(Krild_Holy_Casino_Stone stone, bool won) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)stone == (Object)null)) { Sound val = (Sound)(won ? WinSoundField.GetValue(stone) : LoseSoundField.GetValue(stone)); if ((int)val != 0) { val.Play(((Component)stone).transform.position, 1f, 1f, 1f, 1f); } } } } internal static class ConfigOrderingRuntime { private sealed class ParsedSection { internal readonly Dictionary<string, string[]> Blocks = new Dictionary<string, string[]>(StringComparer.Ordinal); internal readonly List<string> OriginalKeys = new List<string>(); internal string[] TrailingLines = Array.Empty<string>(); } private static readonly string[] SectionOrder = new string[6] { "SPAWN", "STONE", "CHANCES", "GOOD", "BAD", "COMPATIBILITY" }; private static readonly Dictionary<string, string[]> KeyOrder = new Dictionary<string, string[]>(StringComparer.Ordinal) { ["SPAWN"] = new string[2] { "Minimum Spawn(s)", "Maximum Spawn(s)" }, ["STONE"] = new string[2] { "Stone Takes Damages", "Stone Start Value" }, ["CHANCES"] = new string[1] { "Good Chance" }, ["GOOD"] = new string[11] { "Multiply", "Minimum Multiplier", "Maximum Multiplier", "Incremental", "Minimum Start", "Maximum Start", "Minimum Step", "Maximum Step", "Addition", "Minimum Addition", "Maximum Addition" }, ["BAD"] = new string[13] { "Back To Starting Value", "Divide", "Minimum Divide By", "Maximum Divide By", "Decremental", "Minimum Start", "Maximum Start", "Minimum Step", "Maximum Step", "Subtract", "Minimum Subtraction", "Maximum Subtraction", "Reset Incremental" }, ["COMPATIBILITY"] = new string[1] { "Fix LevelScaling" } }; private static bool _writing; private static bool _reportedFailure; internal static ConfigEntryBase[] OrderEntries(ConfigEntryBase[] entries) { if (entries == null) { return Array.Empty<ConfigEntryBase>(); } string[] value; return (from item in entries.Select((ConfigEntryBase entry, int index) => new { Entry = entry, OriginalIndex = index, SectionRank = Rank(SectionOrder, entry.Definition.Section), KeyRank = (KeyOrder.TryGetValue(entry.Definition.Section, out value) ? Rank(value, entry.Definition.Key) : int.MaxValue) }) orderby item.SectionRank, item.KeyRank, item.OriginalIndex select item.Entry).ToArray(); } internal static void RewriteConfigFile(string path) { if (_writing || string.IsNullOrEmpty(path) || !File.Exists(path)) { return; } try { _writing = true; string[] array = File.ReadAllLines(path); int num = Array.FindIndex(array, IsSectionHeader); if (num < 0) { return; } List<string> list = new List<string>(); AppendTrimmed(list, array.Take(num)); list.Add(string.Empty); Dictionary<string, ParsedSection> dictionary = ParseSections(array, num); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); string[] sectionOrder = SectionOrder; foreach (string text in sectionOrder) { if (dictionary.TryGetValue(text, out var value)) { AppendSection(list, text, value); hashSet.Add(text); } } foreach (KeyValuePair<string, ParsedSection> item in dictionary) { if (!hashSet.Contains(item.Key)) { AppendSection(list, item.Key, item.Value); } } while (list.Count > 0 && string.IsNullOrWhiteSpace(list[list.Count - 1])) { list.RemoveAt(list.Count - 1); } list.Add(string.Empty); File.WriteAllLines(path, list, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception ex) { if (!_reportedFailure) { _reportedFailure = true; Plugin.Log.LogError((object)("Failed to preserve Holy Casino Stone Extra config ordering: " + ex)); } } finally { _writing = false; } } private static Dictionary<string, ParsedSection> ParseSections(string[] lines, int start) { Dictionary<string, ParsedSection> dictionary = new Dictionary<string, ParsedSection>(StringComparer.Ordinal); int i = start; while (i < lines.Length) { if (!TryGetSectionName(lines[i], out var section)) { i++; continue; } i++; int num = i; for (; i < lines.Length && !IsSectionHeader(lines[i]); i++) { } dictionary[section] = ParseSection(lines.Skip(num).Take(i - num)); } return dictionary; } private static ParsedSection ParseSection(IEnumerable<string> bodyLines) { ParsedSection parsedSection = new ParsedSection(); List<string> list = new List<string>(); foreach (string bodyLine in bodyLines) { list.Add(bodyLine); if (TryGetSettingKey(bodyLine, out var key)) { string[] value = TrimBlankLines(list).ToArray(); parsedSection.Blocks[key] = value; parsedSection.OriginalKeys.Add(key); list.Clear(); } } parsedSection.TrailingLines = TrimBlankLines(list).ToArray(); return parsedSection; } private static void AppendSection(List<string> output, string name, ParsedSection section) { output.Add("[" + name + "]"); output.Add(string.Empty); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); if (KeyOrder.TryGetValue(name, out var value)) { string[] array = value; foreach (string text in array) { if (section.Blocks.TryGetValue(text, out var value2)) { AppendBlock(output, value2); hashSet.Add(text); } } } foreach (string originalKey in section.OriginalKeys) { if (!hashSet.Contains(originalKey) && section.Blocks.TryGetValue(originalKey, out var value3)) { AppendBlock(output, value3); hashSet.Add(originalKey); } } if (section.TrailingLines.Length != 0) { AppendBlock(output, section.TrailingLines); } } private static void AppendBlock(List<string> output, IEnumerable<string> lines) { AppendTrimmed(output, lines); output.Add(string.Empty); } private static void AppendTrimmed(List<string> output, IEnumerable<string> lines) { output.AddRange(TrimBlankLines(lines)); } private static IEnumerable<string> TrimBlankLines(IEnumerable<string> lines) { string[] array = lines.ToArray(); int i; for (i = 0; i < array.Length && string.IsNullOrWhiteSpace(array[i]); i++) { } int end = array.Length - 1; while (end >= i && string.IsNullOrWhiteSpace(array[end])) { end--; } for (int index = i; index <= end; index++) { yield return array[index]; } } private static bool IsSectionHeader(string line) { string section; return TryGetSectionName(line, out section); } private static bool TryGetSectionName(string line, out string section) { string text = line?.Trim(); if (!string.IsNullOrEmpty(text) && text.Length > 2 && text[0] == '[' && text[text.Length - 1] == ']') { section = text.Substring(1, text.Length - 2); return true; } section = null; return false; } private static bool TryGetSettingKey(string line, out string key) { string text = line?.Trim(); if (string.IsNullOrEmpty(text) || text[0] == '#' || text[0] == '[') { key = null; return false; } int num = text.IndexOf(" = ", StringComparison.Ordinal); if (num <= 0) { key = null; return false; } key = text.Substring(0, num).Trim(); return true; } private static int Rank(string[] order, string value) { int num = Array.IndexOf(order, value); if (num >= 0) { return num; } return int.MaxValue; } } [HarmonyPatch(typeof(ConfigFile), "Save")] internal static class ConfigFileSaveOrderingPatch { [HarmonyPostfix] private static void PreserveRequestedOrder(ConfigFile __instance) { Plugin instance = Plugin.Instance; if ((Object)(object)instance != (Object)null && __instance == ((BaseUnityPlugin)instance).Config) { ConfigOrderingRuntime.RewriteConfigFile(__instance.ConfigFilePath); } } } [HarmonyPatch] internal static class RepoConfigOrderingPatch { [HarmonyPrepare] private static bool Prepare() { if (Chainloader.PluginInfos.ContainsKey("nickklmao.repoconfig")) { return TargetMethod() != null; } return false; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return AccessTools.Method("REPOConfig.ConfigMenu:GetModConfigEntries", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void ReorderHolyCasinoStoneExtra(ref Dictionary<string, ConfigEntryBase[]> __result) { if (__result != null && __result.TryGetValue("Holy Casino Stone Extra", out var value)) { __result["Holy Casino Stone Extra"] = ConfigOrderingRuntime.OrderEntries(value); } } } internal static class LevelScalingCompatibility { private sealed class TrackedValue { internal ValuableObject Valuable; internal float Original; internal float Current; internal bool IsSurplus; } private static FieldInfo _clientTotalMapValueField; private static FieldInfo _valueFromSurplusField; private static FieldInfo _levelTotalValueBrokenField; private static FieldInfo _runStatsInstanceField; private static FieldInfo _runTotalValueBrokenField; private static MethodInfo _globalAddBrokenValueMethod; private static readonly Dictionary<int, TrackedValue> TrackedValues = new Dictionary<int, TrackedValue>(); private static readonly List<int> DeadValueKeys = new List<int>(); private static bool _available; private static bool _reportedRuntimeFailure; private static float _nextReconcileTime; internal static void Initialize() { if (!Chainloader.PluginInfos.TryGetValue("Swaggies.LevelScaling", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null) { Plugin.Log.LogInfo((object)"LevelScaling is not installed; compatibility toggles will remain idle."); return; } Assembly assembly = ((object)value.Instance).GetType().Assembly; Type type = assembly.GetType("LevelScaling.LevelStats.LevelStatsManager"); Type type2 = assembly.GetType("LevelScaling.LevelStats.RunStatsManager"); Type type3 = assembly.GetType("LevelScaling.LevelStats.GlobalStatsManager"); _clientTotalMapValueField = FindField(type, "_clientTotalMapValue", isStatic: true); _valueFromSurplusField = FindField(type, "_valueFromSurplus", isStatic: true); _levelTotalValueBrokenField = FindField(type, "totalValueBroken", isStatic: true); _runStatsInstanceField = FindAnyField(type2, "instance", isStatic: true); _runTotalValueBrokenField = FindField(type2, "totalValueBroken", isStatic: false); _globalAddBrokenValueMethod = ((type3 == null) ? null : type3.GetMethod("AddBrokenValue", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null)); _available = _clientTotalMapValueField != null && _valueFromSurplusField != null && _levelTotalValueBrokenField != null && _runStatsInstanceField != null && _runTotalValueBrokenField != null && _globalAddBrokenValueMethod != null; if (_available) { Plugin.Log.LogInfo((object)$"LevelScaling {value.Metadata.Version} compatibility hooks resolved successfully."); } else { Plugin.Log.LogWarning((object)$"LevelScaling {value.Metadata.Version} was found, but one or more stats fields changed. Holy Casino Stone Extra will not mutate LevelScaling stats."); } } internal static void OnValueReplaced(ValuableObject valuable, float oldOriginal, float oldCurrent, float newOriginal, float newCurrent, float valueAddedByLevelScaling, bool wasSurplus) { if (!_available || !SemiFunc.RunIsLevel()) { return; } try { if (Plugin.Instance.FixLevelScaling.Value) { float num = ReadFloat(_clientTotalMapValueField, null); if (wasSurplus) { WriteFloat(_clientTotalMapValueField, null, Mathf.Max(0f, num - valueAddedByLevelScaling)); float num2 = ReadFloat(_valueFromSurplusField, null); WriteFloat(_valueFromSurplusField, null, Mathf.Max(0f, num2 - (float)Mathf.FloorToInt(newCurrent))); } else { float num3 = newOriginal - oldOriginal - valueAddedByLevelScaling; WriteFloat(_clientTotalMapValueField, null, Mathf.Max(0f, num + num3)); } } if (Plugin.Instance.FixLevelScaling.Value) { float num4 = OutstandingDamage(oldOriginal, oldCurrent); ApplyBrokenDelta(OutstandingDamage(newOriginal, newCurrent) - num4); } ObserveKnownValue(valuable); } catch (Exception exception) { DisableAfterRuntimeFailure(exception); } } internal static void ObserveKnownValue(ValuableObject valuable) { if (_available && !((Object)(object)valuable == (Object)null) && SemiFunc.RunIsLevel() && CasinoStoneRuntime.HasEstablishedValue(valuable)) { int instanceID = ((Object)valuable).GetInstanceID(); TrackedValues[instanceID] = new TrackedValue { Valuable = valuable, Original = CasinoStoneRuntime.GetOriginalValue(valuable), Current = CasinoStoneRuntime.GetCurrentValue(valuable), IsSurplus = ((Object)(object)((Component)valuable).GetComponent<SurplusValuable>() != (Object)null) }; } } internal static void ForgetValue(ValuableObject valuable) { if ((Object)(object)valuable != (Object)null) { TrackedValues.Remove(((Object)valuable).GetInstanceID()); } } internal static void ReconcileTrackedValues() { if (!_available) { return; } if (!SemiFunc.RunIsLevel()) { TrackedValues.Clear(); DeadValueKeys.Clear(); } else { if (Time.unscaledTime < _nextReconcileTime) { return; } _nextReconcileTime = Time.unscaledTime + 0.1f; try { DeadValueKeys.Clear(); foreach (KeyValuePair<int, TrackedValue> trackedValue in TrackedValues) { TrackedValue value = trackedValue.Value; if ((Object)(object)value.Valuable == (Object)null) { DeadValueKeys.Add(trackedValue.Key); } else { if (!CasinoStoneRuntime.HasEstablishedValue(value.Valuable)) { continue; } float originalValue = CasinoStoneRuntime.GetOriginalValue(value.Valuable); float currentValue = CasinoStoneRuntime.GetCurrentValue(value.Valuable); bool flag = (Object)(object)((Component)value.Valuable).GetComponent<SurplusValuable>() != (Object)null; if (!Mathf.Approximately(originalValue, value.Original) || !Mathf.Approximately(currentValue, value.Current) || flag != value.IsSurplus) { float num = 0f; float num2 = 0f; if (Plugin.Instance.FixLevelScaling.Value && !value.IsSurplus && !flag) { num = originalValue - value.Original; float num3 = ReadFloat(_clientTotalMapValueField, null); WriteFloat(_clientTotalMapValueField, null, Mathf.Max(0f, num3 + num)); } if (Plugin.Instance.FixLevelScaling.Value) { num2 = OutstandingDamage(originalValue, currentValue) - OutstandingDamage(value.Original, value.Current); ApplyBrokenDelta(num2); } value.Original = originalValue; value.Current = currentValue; value.IsSurplus = flag; if (!Mathf.Approximately(num, 0f) || !Mathf.Approximately(num2, 0f)) { Plugin.Log.LogDebug((object)$"Reconciled an external valuable change: map delta {num:0}, broken delta {num2:0}."); } } } } foreach (int deadValueKey in DeadValueKeys) { TrackedValues.Remove(deadValueKey); } } catch (Exception exception) { DisableAfterRuntimeFailure(exception); } } } internal static void Reset() { TrackedValues.Clear(); DeadValueKeys.Clear(); _available = false; } private static float OutstandingDamage(float original, float current) { return Mathf.Max(0f, original - current); } private static void ApplyBrokenDelta(float requestedDelta) { if (Mathf.Approximately(requestedDelta, 0f)) { return; } float num = ReadFloat(_levelTotalValueBrokenField, null); float num2 = Mathf.Max(0f, num + requestedDelta); float num3 = num2 - num; if (!Mathf.Approximately(num3, 0f)) { WriteFloat(_levelTotalValueBrokenField, null, num2); object value = _runStatsInstanceField.GetValue(null); if (value != null) { float num4 = ReadFloat(_runTotalValueBrokenField, value); WriteFloat(_runTotalValueBrokenField, value, Mathf.Max(0f, num4 + num3)); } _globalAddBrokenValueMethod.Invoke(null, new object[1] { num3 }); } } private static void DisableAfterRuntimeFailure(Exception exception) { if (!_reportedRuntimeFailure) { _reportedRuntimeFailure = true; Plugin.Log.LogError((object)("LevelScaling compatibility correction failed and has been disabled for this process: " + exception)); } _available = false; TrackedValues.Clear(); DeadValueKeys.Clear(); } private static FieldInfo FindField(Type type, string name, bool isStatic) { if (type == null) { return null; } FieldInfo field = type.GetField(name, (BindingFlags)(0x30 | (isStatic ? 8 : 4))); if (!(field != null) || (!(field.FieldType == typeof(float)) && !(field.FieldType == typeof(int)))) { return null; } return field; } private static FieldInfo FindAnyField(Type type, string name, bool isStatic) { return type?.GetField(name, (BindingFlags)(0x30 | (isStatic ? 8 : 4))); } private static float ReadFloat(FieldInfo field, object instance) { return Convert.ToSingle(field.GetValue(instance)); } private static void WriteFloat(FieldInfo field, object instance, float value) { if (field.FieldType == typeof(int)) { field.SetValue(instance, Mathf.RoundToInt(value)); } else { field.SetValue(instance, value); } } } internal static class OriginalLoopSoundCompatibility { private static readonly MethodInfo OriginalStartMethod = AccessTools.DeclaredMethod(typeof(Krild_Holy_Casino_Stone), "Start", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo OriginalUpdateMethod = AccessTools.DeclaredMethod(typeof(Krild_Holy_Casino_Stone), "Update", Type.EmptyTypes, (Type[])null); private static readonly FieldInfo PhysGrabObjectField = AccessTools.DeclaredField(typeof(Krild_Holy_Casino_Stone), "physgrabObject"); private static readonly FieldInfo LoopSoundField = AccessTools.DeclaredField(typeof(Krild_Holy_Casino_Stone), "loopKrild"); private static readonly MethodInfo CurrentPlayLoopMethod = AccessTools.DeclaredMethod(typeof(Sound), "PlayLoop", new Type[5] { typeof(bool), typeof(float), typeof(float), typeof(float), typeof(float) }, (Type[])null); internal static MethodBase StartTarget => OriginalStartMethod; internal static bool Validate(out string error) { if (!ValidateInstanceVoidMethod(OriginalStartMethod, "Start", 0, out error) || !ValidateInstanceVoidMethod(OriginalUpdateMethod, "Update", 0, out error)) { return false; } if (!ValidateField(PhysGrabObjectField, "physgrabObject", typeof(PhysGrabObject), out error) || !ValidateField(LoopSoundField, "loopKrild", typeof(Sound), out error)) { return false; } if (!ValidateInstanceVoidMethod(CurrentPlayLoopMethod, "Sound.PlayLoop", 5, out error)) { return false; } ParameterInfo[] parameters = CurrentPlayLoopMethod.GetParameters(); Type[] array = new Type[5] { typeof(bool), typeof(float), typeof(float), typeof(float), typeof(float) }; for (int i = 0; i < array.Length; i++) { if (parameters[i].ParameterType != array[i]) { error = $"Sound.PlayLoop parameter {i + 1} is {parameters[i].ParameterType.FullName}, " + "expected " + array[i].FullName + "."; return false; } } error = null; return true; } internal static void AttachReplacement(Krild_Holy_Casino_Stone original) { if (!((Object)(object)original == (Object)null)) { ((Behaviour)original).enabled = false; object? value = PhysGrabObjectField.GetValue(original); PhysGrabObject physGrabObject = (PhysGrabObject)((value is PhysGrabObject) ? value : null); object? value2 = LoopSoundField.GetValue(original); Sound loopSound = (Sound)((value2 is Sound) ? value2 : null); OriginalLoopSoundReplacement originalLoopSoundReplacement = ((Component)original).GetComponent<OriginalLoopSoundReplacement>(); if ((Object)(object)originalLoopSoundReplacement == (Object)null) { originalLoopSoundReplacement = ((Component)original).gameObject.AddComponent<OriginalLoopSoundReplacement>(); } originalLoopSoundReplacement.Initialize(physGrabObject, loopSound); } } private static bool ValidateInstanceVoidMethod(MethodInfo method, string name, int parameterCount, out string error) { if (method == null) { error = "Required method " + name + " was not found."; return false; } if (method.IsStatic || method.ReturnType != typeof(void) || method.GetParameters().Length != parameterCount) { error = "Required method " + name + " has an unexpected signature."; return false; } error = null; return true; } private static bool ValidateField(FieldInfo field, string name, Type expectedType, out string error) { if (field == null) { error = "Required original-mod field " + name + " was not found."; return false; } if (field.IsStatic || field.FieldType != expectedType) { error = "Original-mod field " + name + " is " + field.FieldType.FullName + ", expected instance field " + expectedType.FullName + "."; return false; } error = null; return true; } } internal sealed class OriginalLoopSoundReplacement : MonoBehaviour { private PhysGrabObject _physGrabObject; private Sound _loopSound; internal void Initialize(PhysGrabObject physGrabObject, Sound loopSound) { _physGrabObject = physGrabObject; _loopSound = loopSound; ((Behaviour)this).enabled = (Object)(object)_physGrabObject != (Object)null && _loopSound != null; } private void Update() { if (!((Object)(object)_physGrabObject == (Object)null) && _loopSound != null && !((Object)(object)_loopSound.Source == (Object)null) && _loopSound.Sounds != null && _loopSound.Sounds.Length != 0) { _loopSound.PlayLoop(_physGrabObject.grabbed, 1f, 1f, 1f, 1f); } } } [HarmonyPatch] internal static class OriginalCasinoStoneStartPatch { [HarmonyTargetMethod] private static MethodBase TargetMethod() { return OriginalLoopSoundCompatibility.StartTarget; } [HarmonyPostfix] private static void Postfix(Krild_Holy_Casino_Stone __instance) { OriginalLoopSoundCompatibility.AttachReplacement(__instance); } } [BepInPlugin("mathe.holycasinostoneextra", "Holy Casino Stone Extra", "0.1.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "mathe.holycasinostoneextra"; public const string PluginName = "Holy Casino Stone Extra"; public const string PluginVersion = "0.1.2"; public const string OriginalPluginGuid = "KrildHolyCasinoStone"; public const string RepoLibGuid = "REPOLib"; public const string LevelScalingGuid = "Swaggies.LevelScaling"; public const string RepoConfigGuid = "nickklmao.repoconfig"; private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal ConfigEntry<int> MinimumSpawnInLevel { get; private set; } internal ConfigEntry<int> MaximumSpawnInLevel { get; private set; } internal ConfigEntry<bool> StoneTakesDamages { get; private set; } internal ConfigEntry<string> StoneStartValue { get; private set; } internal ConfigEntry<int> GoodChance { get; private set; } internal ConfigEntry<bool> GoodMultiply { get; private set; } internal ConfigEntry<string> GoodMinimumMultiplier { get; private set; } internal ConfigEntry<string> GoodMaximumMultiplier { get; private set; } internal ConfigEntry<bool> GoodIncremental { get; private set; } internal ConfigEntry<string> GoodIncrementalMinimumStart { get; private set; } internal ConfigEntry<string> GoodIncrementalMaximumStart { get; private set; } internal ConfigEntry<string> GoodIncrementalMinimumStep { get; private set; } internal ConfigEntry<string> GoodIncrementalMaximumStep { get; private set; } internal ConfigEntry<bool> GoodAddition { get; private set; } internal ConfigEntry<string> GoodMinimumAddition { get; private set; } internal ConfigEntry<string> GoodMaximumAddition { get; private set; } internal ConfigEntry<bool> BadBackToStartingValue { get; private set; } internal ConfigEntry<bool> BadDivide { get; private set; } internal ConfigEntry<string> BadMinimumDivideBy { get; private set; } internal ConfigEntry<string> BadMaximumDivideBy { get; private set; } internal ConfigEntry<bool> BadDecremental { get; private set; } internal ConfigEntry<string> BadDecrementalMinimumStart { get; private set; } internal ConfigEntry<string> BadDecrementalMaximumStart { get; private set; } internal ConfigEntry<string> BadDecrementalMinimumStep { get; private set; } internal ConfigEntry<string> BadDecrementalMaximumStep { get; private set; } internal ConfigEntry<bool> BadSubtract { get; private set; } internal ConfigEntry<string> BadMinimumSubtraction { get; private set; } internal ConfigEntry<string> BadMaximumSubtraction { get; private set; } internal ConfigEntry<bool> BadResetIncremental { get; private set; } internal ConfigEntry<bool> FixLevelScaling { get; private set; } private void Awake() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfiguration(); if (!CasinoStoneRuntime.Validate(out var error) || !OriginalLoopSoundCompatibility.Validate(out error) || !SpawnController.Validate(out error)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Holy Casino Stone Extra is disabled because the installed game/original mod does not match its required runtime fields: " + error)); return; } LevelScalingCompatibility.Initialize(); _harmony = new Harmony("mathe.holycasinostoneextra"); _harmony.PatchAll(); ConfigOrderingRuntime.RewriteConfigFile(((BaseUnityPlugin)this).Config.ConfigFilePath); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Holy Casino Stone Extra 0.1.2 loaded. Host-authoritative casino and spawn patches are active."); } private void LateUpdate() { StoneProgressionRuntime.UpdateLifecycle(); LevelScalingCompatibility.ReconcileTrackedValues(); } private void OnDestroy() { CasinoRollScheduler.Clear(); StoneProgressionRuntime.Clear(); LevelScalingCompatibility.Reset(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; Log = null; } } private void BindConfiguration() { //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Expected O, but got Unknown //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Expected O, but got Unknown //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Expected O, but got Unknown bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>("SPAWN", "Minimum Spawn In Level", 0, (ConfigDescription)null); ConfigEntry<int> val2 = ((BaseUnityPlugin)this).Config.Bind<int>("SPAWN", "Maximum Spawn In Level", 2, (ConfigDescription)null); ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("GENERAL", "Item Damages", true, (ConfigDescription)null); ConfigEntry<int> val4 = ((BaseUnityPlugin)this).Config.Bind<int>("CHANCES", "Multiplier Chance", 80, (ConfigDescription)null); ConfigEntry<float> val5 = ((BaseUnityPlugin)this).Config.Bind<float>("CHANCES", "Minimum Multiplier", 1f, (ConfigDescription)null); ConfigEntry<float> val6 = ((BaseUnityPlugin)this).Config.Bind<float>("CHANCES", "Maximum Multiplier", 2f, (ConfigDescription)null); ConfigEntry<int> val7 = ((BaseUnityPlugin)this).Config.Bind<int>("CHANCES", "Reset Chance", 20, (ConfigDescription)null); ConfigEntry<bool> val8 = ((BaseUnityPlugin)this).Config.Bind<bool>("CHANCES", "Toggle Reset", true, (ConfigDescription)null); ConfigEntry<float> val9 = ((BaseUnityPlugin)this).Config.Bind<float>("CHANCES", "Minimum Divider", 2f, (ConfigDescription)null); ConfigEntry<float> val10 = ((BaseUnityPlugin)this).Config.Bind<float>("CHANCES", "Maximum Divider", 2f, (ConfigDescription)null); ConfigEntry<bool> val11 = ((BaseUnityPlugin)this).Config.Bind<bool>("COMPATIBILITY", "Fix LevelScaling Level Max Value", true, (ConfigDescription)null); ConfigEntry<bool> val12 = ((BaseUnityPlugin)this).Config.Bind<bool>("COMPATIBILITY", "Fix LevelScaling Item Damages From Cube", true, (ConfigDescription)null); int value = val.Value; int value2 = val2.Value; bool value3 = val3.Value; int value4 = val4.Value; float defaultValue = ClampOneToTen(val5.Value); float defaultValue2 = ClampOneToTen(val6.Value); bool value5 = val8.Value; bool flag = !val8.Value && val7.Value > 0; float defaultValue3 = ClampOneToTen(val9.Value); float defaultValue4 = ClampOneToTen(val10.Value); bool flag2 = val11.Value && val12.Value; ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val2).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val3).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val4).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val5).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val6).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val7).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val8).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val9).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val10).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val11).Definition); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val12).Definition); MinimumSpawnInLevel = ((BaseUnityPlugin)this).Config.Bind<int>("SPAWN", "Minimum Spawn(s)", value, new ConfigDescription("Minimum number of Holy Casino Stones selected for each generated level. Capped at Maximum Spawn(s).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())); MaximumSpawnInLevel = ((BaseUnityPlugin)this).Config.Bind<int>("SPAWN", "Maximum Spawn(s)", value2, new ConfigDescription("Maximum number of Holy Casino Stones selected for each generated level.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())); StoneTakesDamages = ((BaseUnityPlugin)this).Config.Bind<bool>("STONE", "Stone Takes Damages", value3, "When enabled, physical breaks reduce the stone's value. When disabled, its break value loss is forced to zero before it is networked or recorded by LevelScaling."); StoneStartValue = ((BaseUnityPlugin)this).Config.Bind<string>("STONE", "Stone Start Value", "100", "Dollar value assigned to every Holy Casino Stone when it initializes."); GoodChance = ((BaseUnityPlugin)this).Config.Bind<int>("CHANCES", "Good Chance", value4, new ConfigDescription("Chance that an eligible impact selects a GOOD operation. BAD chance is automatically 100 minus this value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); GoodMultiply = ((BaseUnityPlugin)this).Config.Bind<bool>("GOOD", "Multiply", true, "Adds Multiply to the random pool of enabled GOOD operations."); GoodMinimumMultiplier = QuarterStepSlider("GOOD", "Minimum Multiplier", defaultValue, "Minimum random multiplier for Multiply."); GoodMaximumMultiplier = QuarterStepSlider("GOOD", "Maximum Multiplier", defaultValue2, "Maximum random multiplier for Multiply. Bounds are reordered when necessary."); GoodIncremental = ((BaseUnityPlugin)this).Config.Bind<bool>("GOOD", "Incremental", false, "Adds Incremental to the random GOOD pool. It multiplies by its current factor, then multiplies that factor by a random step for its next selection."); GoodIncrementalMinimumStart = QuarterStepSlider("GOOD", "Minimum Start", 1.5f, "Minimum initial Incremental multiplier."); GoodIncrementalMaximumStart = QuarterStepSlider("GOOD", "Maximum Start", 1.5f, "Maximum initial Incremental multiplier."); GoodIncrementalMinimumStep = QuarterStepSlider("GOOD", "Minimum Step", 2f, "Minimum factor applied to the Incremental multiplier after it is selected."); GoodIncrementalMaximumStep = QuarterStepSlider("GOOD", "Maximum Step", 2f, "Maximum factor applied to the Incremental multiplier after it is selected."); GoodAddition = ((BaseUnityPlugin)this).Config.Bind<bool>("GOOD", "Addition", false, "Adds Addition to the random pool of enabled GOOD operations."); GoodMinimumAddition = ((BaseUnityPlugin)this).Config.Bind<string>("GOOD", "Minimum Addition", "100", "Minimum random dollar amount added by Addition."); GoodMaximumAddition = ((BaseUnityPlugin)this).Config.Bind<string>("GOOD", "Maximum Addition", "1000", "Maximum random dollar amount added by Addition. Bounds are reordered when necessary."); BadBackToStartingValue = ((BaseUnityPlugin)this).Config.Bind<bool>("BAD", "Back To Starting Value", value5, "Adds a reset to Stone Start Value to the random pool of enabled BAD operations."); BadDivide = ((BaseUnityPlugin)this).Config.Bind<bool>("BAD", "Divide", flag, "Adds Divide to the random pool of enabled BAD operations."); BadMinimumDivideBy = QuarterStepSlider("BAD", "Minimum Divide By", defaultValue3, "Minimum random divisor for Divide."); BadMaximumDivideBy = QuarterStepSlider("BAD", "Maximum Divide By", defaultValue4, "Maximum random divisor for Divide. Bounds are reordered when necessary."); BadDecremental = ((BaseUnityPlugin)this).Config.Bind<bool>("BAD", "Decremental", false, "Adds Decremental to the random BAD pool. It divides by its current factor, then multiplies that factor by a random step for its next selection."); BadDecrementalMinimumStart = QuarterStepSlider("BAD", "Minimum Start", 1f, "Minimum initial Decremental divisor."); BadDecrementalMaximumStart = QuarterStepSlider("BAD", "Maximum Start", 2f, "Maximum initial Decremental divisor."); BadDecrementalMinimumStep = QuarterStepSlider("BAD", "Minimum Step", 1f, "Minimum factor applied to the Decremental divisor after it is selected."); BadDecrementalMaximumStep = QuarterStepSlider("BAD", "Maximum Step", 2f, "Maximum factor applied to the Decremental divisor after it is selected."); BadSubtract = ((BaseUnityPlugin)this).Config.Bind<bool>("BAD", "Subtract", false, "Adds Subtract to the random pool of enabled BAD operations."); BadMinimumSubtraction = ((BaseUnityPlugin)this).Config.Bind<string>("BAD", "Minimum Subtraction", "100", "Minimum random dollar amount removed by Subtract."); BadMaximumSubtraction = ((BaseUnityPlugin)this).Config.Bind<string>("BAD", "Maximum Subtraction", "2000", "Maximum random dollar amount removed by Subtract. Bounds are reordered when necessary."); BadResetIncremental = ((BaseUnityPlugin)this).Config.Bind<bool>("BAD", "Reset Incremental", false, "When enabled, every BAD roll resets Incremental so its next selection chooses a new starting multiplier."); FixLevelScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("COMPATIBILITY", "Fix LevelScaling", flag2, "Reconciles both LevelScaling's map maximum and outstanding Broken value when valuables lose, regain, or replace value."); ((BaseUnityPlugin)this).Config.Save(); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private ConfigEntry<string> QuarterStepSlider(string section, string key, float defaultValue, string description) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<string>(section, key, FormatQuarterStep(defaultValue), new ConfigDescription(description + " Selectable from 1 to 10 in 0.25 steps.", (AcceptableValueBase)(object)new AcceptableValueList<string>(CreateQuarterStepOptions()), Array.Empty<object>())); } internal static float ReadQuarterStep(ConfigEntry<string> entry) { return ParseConfigNumber(entry?.Value, 1f); } internal static float ReadNumberInput(ConfigEntry<string> entry, float fallback) { return Math.Max(0f, ParseConfigNumber(entry?.Value, fallback)); } private static string[] CreateQuarterStepOptions() { string[] array = new string[37]; for (int i = 0; i < array.Length; i++) { array[i] = FormatQuarterStep(1f + (float)i * 0.25f); } return array; } private static string FormatQuarterStep(float value) { return ((float)Math.Round(Math.Max(1f, Math.Min(10f, value)) * 4f) / 4f).ToString("0.##", CultureInfo.InvariantCulture); } private static float ParseConfigNumber(string text, float fallback) { string text2 = text?.Trim().Replace(',', '.'); if (!string.IsNullOrEmpty(text2) && float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !float.IsNaN(result) && !float.IsInfinity(result)) { return result; } return fallback; } private static float ClampOneToTen(float value) { return Math.Max(1f, Math.Min(10f, value)); } internal static void OrderedRange(float configuredMinimum, float configuredMaximum, out float minimum, out float maximum) { minimum = Math.Min(configuredMinimum, configuredMaximum); maximum = Math.Max(configuredMinimum, configuredMaximum); } } internal static class SpawnController { private const string CasinoPrefabName = "Holy Casino Stone"; private static readonly Dictionary<string, FieldInfo> ValuableListFields = new Dictionary<string, FieldInfo>(StringComparer.Ordinal) { ["01 Tiny"] = AccessTools.Field(typeof(ValuableDirector), "tinyValuables"), ["02 Small"] = AccessTools.Field(typeof(ValuableDirector), "smallValuables"), ["03 Medium"] = AccessTools.Field(typeof(ValuableDirector), "mediumValuables"), ["04 Big"] = AccessTools.Field(typeof(ValuableDirector), "bigValuables"), ["05 Wide"] = AccessTools.Field(typeof(ValuableDirector), "wideValuables"), ["06 Tall"] = AccessTools.Field(typeof(ValuableDirector), "tallValuables"), ["07 Very Tall"] = AccessTools.Field(typeof(ValuableDirector), "veryTallValuables") }; private static PrefabRef _casinoPrefab; private static int _targetCount; private static int _spawnedCount; private static bool _warnedNoReplacement; internal static bool Validate(out string error) { if (AccessTools.Method(typeof(ValuableDirector), "SpawnValuable", new Type[3] { typeof(PrefabRef), typeof(ValuableVolume), typeof(string) }, (Type[])null) == null) { error = "ValuableDirector.SpawnValuable(PrefabRef, ValuableVolume, string) was not found."; return false; } if (AccessTools.Method(typeof(ValuableDirector), "SetupHost", (Type[])null, (Type[])null) == null || AccessTools.Method(typeof(ValuableDirector), "VolumesAndSwitchSetup", (Type[])null, (Type[])null) == null) { error = "the current ValuableDirector setup methods were not found."; return false; } foreach (KeyValuePair<string, FieldInfo> valuableListField in ValuableListFields) { if (valuableListField.Value == null || valuableListField.Value.FieldType != typeof(List<PrefabRef>)) { error = "the ValuableDirector list for " + valuableListField.Key + " was not found as List<PrefabRef>."; return false; } } error = null; return true; } internal static void BeginLevel() { _casinoPrefab = ((IEnumerable<PrefabRef>)Valuables.RegisteredValuables).FirstOrDefault((Func<PrefabRef, bool>)IsCasinoPrefab) ?? ((IEnumerable<PrefabRef>)Valuables.AllValuables).FirstOrDefault((Func<PrefabRef, bool>)IsCasinoPrefab); _spawnedCount = 0; _warnedNoReplacement = false; int value = Plugin.Instance.MaximumSpawnInLevel.Value; int num = Math.Min(Plugin.Instance.MinimumSpawnInLevel.Value, value); _targetCount = Random.Range(num, value + 1); if (_casinoPrefab == null) { _targetCount = 0; Plugin.Log.LogError((object)"Could not find REPOLib's registered Holy Casino Stone prefab. Spawn limits cannot be applied this level."); } else { Plugin.Log.LogInfo((object)($"Holy Casino Stone spawn target for this level: {_targetCount} " + $"(configured {num}-{value}).")); } } internal static bool PrepareSpawn(ValuableDirector director, ref PrefabRef selectedPrefab, string path) { if (_casinoPrefab == null) { return false; } if (_spawnedCount < _targetCount) { selectedPrefab = _casinoPrefab; return true; } if (!IsCasinoPrefab(selectedPrefab)) { return false; } PrefabRef val = FindReplacement(director, path); if (val != null) { selectedPrefab = val; return false; } if (!_warnedNoReplacement) { _warnedNoReplacement = true; Plugin.Log.LogWarning((object)"The game selected an extra Holy Casino Stone, but no same-size replacement was available. The configured maximum may be exceeded for this level."); } return true; } internal static void CountCasinoSpawn(bool casinoSpawn) { if (casinoSpawn) { _spawnedCount++; } } internal static void EndLevelSetup() { if (_casinoPrefab != null) { if (_spawnedCount < _targetCount) { Plugin.Log.LogWarning((object)$"Only {_spawnedCount} of the {_targetCount} targeted Holy Casino Stones could be spawned because the level generated too few valuable slots."); } else { Plugin.Log.LogInfo((object)$"Holy Casino Stone level spawn count finalized at {_spawnedCount}."); } } } private static PrefabRef FindReplacement(ValuableDirector director, string path) { if ((Object)(object)director == (Object)null || path == null || !ValuableListFields.TryGetValue(path, out var value) || !(value?.GetValue(director) is List<PrefabRef> source)) { return null; } List<PrefabRef> list = source.Where((PrefabRef candidate) => candidate != null && !IsCasinoPrefab(candidate)).ToList(); if (list.Count != 0) { return list[Random.Range(0, list.Count)]; } return null; } private static bool IsCasinoPrefab(PrefabRef prefab) { if (prefab != null) { return string.Equals(((PrefabRef<GameObject>)(object)prefab).PrefabName, "Holy Casino Stone", StringComparison.OrdinalIgnoreCase); } return false; } } [HarmonyPatch(typeof(ValuableDirector))] internal static class ValuableDirectorSpawnPatches { [HarmonyPatch("SetupHost")] [HarmonyPrefix] private static void BeginHostSetup() { SpawnController.BeginLevel(); } [HarmonyPatch("SpawnValuable")] [HarmonyPrefix] private static void ControlCasinoSelection(ValuableDirector __instance, ref PrefabRef _valuable, string _path, out bool __state) { __state = SpawnController.PrepareSpawn(__instance, ref _valuable, _path); } [HarmonyPatch("SpawnValuable")] [HarmonyPostfix] private static void CountCasinoSelection(bool __state) { SpawnController.CountCasinoSpawn(__state); } [HarmonyPatch("VolumesAndSwitchSetup")] [HarmonyPrefix] private static void ReportFinalSpawnCount() { SpawnController.EndLevelSetup(); } } internal static class StoneProgressionRuntime { private sealed class StoneState { internal bool IncrementalInitialized; internal float IncrementalMultiplier; internal bool DecrementalInitialized; internal float DecrementalDivisor; } private static readonly Dictionary<int, StoneState> States = new Dictionary<int, StoneState>(); private static bool _wasInLevel; internal static void ApplyConfiguredStartingValue(Krild_Holy_Casino_Stone stone, ValuableObject valuable) { if (!((Object)(object)stone == (Object)null) && !((Object)(object)valuable == (Object)null) && SemiFunc.IsMasterClientOrSingleplayer()) { GetState(stone); float originalValue = CasinoStoneRuntime.GetOriginalValue(valuable); float currentValue = CasinoStoneRuntime.GetCurrentValue(valuable); float num = CasinoStoneRuntime.SanitizeDollarValue(Plugin.ReadNumberInput(Plugin.Instance.StoneStartValue, 500f), 500f); CasinoStoneRuntime.SetLocalDollarValue(valuable, num); CasinoStoneRuntime.SetOriginalCounter(stone, num); LevelScalingCompatibility.OnValueReplaced(valuable, originalValue, currentValue, num, num, 0f, (Object)(object)((Component)valuable).GetComponent<SurplusValuable>() != (Object)null); } } internal static bool TryCalculateOutcome(Krild_Holy_Casino_Stone stone, float baseValue, bool good, out float newValue, out string outcome, out bool setValue) { StoneState state = GetState(stone); Plugin instance = Plugin.Instance; if (good) { return TryCalculateGood(state, instance, baseValue, out newValue, out outcome, out setValue); } return TryCalculateBad(state, instance, baseValue, out newValue, out outcome, out setValue); } internal static void Forget(Krild_Holy_Casino_Stone stone) { if ((Object)(object)stone != (Object)null) { States.Remove(((Object)stone).GetInstanceID()); } } internal static void UpdateLifecycle() { bool num = SemiFunc.RunIsLevel(); if (!num && _wasInLevel) { States.Clear(); } _wasInLevel = num; } internal static void Clear() { States.Clear(); _wasInLevel = false; } private static bool TryCalculateGood(StoneState state, Plugin config, float baseValue, out float newValue, out string outcome, out bool setValue) { int num = (config.GoodMultiply.Value ? 1 : 0) + (config.GoodIncremental.Value ? 1 : 0) + (config.GoodAddition.Value ? 1 : 0); if (num == 0) { newValue = baseValue; outcome = "no enabled GOOD operation"; setValue = false; return false; } int num2 = Random.Range(0, num); if (config.GoodMultiply.Value && num2-- == 0) { float num3 = RandomConfiguredRange(Plugin.ReadQuarterStep(config.GoodMinimumMultiplier), Plugin.ReadQuarterStep(config.GoodMaximumMultiplier)); newValue = baseValue * num3; outcome = $"GOOD multiply {num3:0.###}x"; setValue = true; return true; } if (config.GoodIncremental.Value && num2-- == 0) { if (!state.IncrementalInitialized) { state.IncrementalMultiplier = RandomConfiguredRange(Plugin.ReadQuarterStep(config.GoodIncrementalMinimumStart), Plugin.ReadQuarterStep(config.GoodIncrementalMaximumStart)); state.IncrementalInitialized = true; } float incrementalMultiplier = state.IncrementalMultiplier; float num4 = RandomConfiguredRange(Plugin.ReadQuarterStep(config.GoodIncrementalMinimumStep), Plugin.ReadQuarterStep(config.GoodIncrementalMaximumStep)); state.IncrementalMultiplier = SanitizeProgressionFactor(incrementalMultiplier * num4); newValue = baseValue * incrementalMultiplier; outcome = $"GOOD incremental {incrementalMultiplier:0.###}x; next {state.IncrementalMultiplier:0.###}x"; setValue = true; return true; } float num5 = Mathf.Max(0f, RandomConfiguredRange(Plugin.ReadNumberInput(config.GoodMinimumAddition, 100f), Plugin.ReadNumberInput(config.GoodMaximumAddition, 1000f))); newValue = baseValue + num5; outcome = $"GOOD addition ${num5:0}"; setValue = true; return true; } private static bool TryCalculateBad(StoneState state, Plugin config, float baseValue, out float newValue, out string outcome, out bool setValue) { bool value = config.BadResetIncremental.Value; if (value) { state.IncrementalInitialized = false; state.IncrementalMultiplier = 0f; } int num = (config.BadBackToStartingValue.Value ? 1 : 0) + (config.BadDivide.Value ? 1 : 0) + (config.BadDecremental.Value ? 1 : 0) + (config.BadSubtract.Value ? 1 : 0); if (num == 0) { newValue = baseValue; outcome = (value ? "BAD reset incremental; no enabled value operation" : "no enabled BAD operation"); setValue = false; return value; } int num2 = Random.Range(0, num); if (config.BadBackToStartingValue.Value && num2-- == 0) { newValue = CasinoStoneRuntime.SanitizeDollarValue(Plugin.ReadNumberInput(config.StoneStartValue, 500f), 500f); outcome = "BAD back to starting value"; } else if (config.BadDivide.Value && num2-- == 0) { float num3 = RandomConfiguredRange(Plugin.ReadQuarterStep(config.BadMinimumDivideBy), Plugin.ReadQuarterStep(config.BadMaximumDivideBy)); newValue = baseValue / num3; outcome = $"BAD divide by {num3:0.###}"; } else if (config.BadDecremental.Value && num2-- == 0) { if (!state.DecrementalInitialized) { state.DecrementalDivisor = RandomConfiguredRange(Plugin.ReadQuarterStep(config.BadDecrementalMinimumStart), Plugin.ReadQuarterStep(config.BadDecrementalMaximumStart)); state.DecrementalInitialized = true; } float decrementalDivisor = state.DecrementalDivisor; float num4 = RandomConfiguredRange(Plugin.ReadQuarterStep(config.BadDecrementalMinimumStep), Plugin.ReadQuarterStep(config.BadDecrementalMaximumStep)); state.DecrementalDivisor = SanitizeProgressionFactor(decrementalDivisor * num4); newValue = baseValue / decrementalDivisor; outcome = $"BAD decremental divide by {decrementalDivisor:0.###}; next {state.DecrementalDivisor:0.###}"; } else { float num5 = Mathf.Max(0f, RandomConfiguredRange(Plugin.ReadNumberInput(config.BadMinimumSubtraction, 100f), Plugin.ReadNumberInput(config.BadMaximumSubtraction, 1000f))); newValue = baseValue - num5; outcome = $"BAD subtraction ${num5:0}"; } if (value) { outcome += "; reset incremental"; } setValue = true; return true; } private static StoneState GetState(Krild_Holy_Casino_Stone stone) { int instanceID = ((Object)stone).GetInstanceID(); if (!States.TryGetValue(instanceID, out var value)) { value = new StoneState(); States[instanceID] = value; } return value; } private static float RandomConfiguredRange(float configuredMinimum, float configuredMaximum) { Plugin.OrderedRange(configuredMinimum, configuredMaximum, out var minimum, out var maximum); return Random.Range(minimum, maximum); } private static float SanitizeProgressionFactor(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { return 2E+09f; } return Mathf.Clamp(value, 1f, 2E+09f); } } }