using 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(); 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() != (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() != (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() != (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(); 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(); Krild_Holy_Casino_Stone component2 = ((Component)__instance).GetComponent(); 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 PendingRolls = new Dictionary(); private static readonly Dictionary PendingResultSounds = new Dictionary(); 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(); 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(); } 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 Blocks = new Dictionary(StringComparer.Ordinal); internal readonly List OriginalKeys = new List(); internal string[] TrailingLines = Array.Empty(); } private static readonly string[] SectionOrder = new string[6] { "SPAWN", "STONE", "CHANCES", "GOOD", "BAD", "COMPATIBILITY" }; private static readonly Dictionary KeyOrder = new Dictionary(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(); } 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 list = new List(); AppendTrimmed(list, array.Take(num)); list.Add(string.Empty); Dictionary dictionary = ParseSections(array, num); HashSet hashSet = new HashSet(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 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 ParseSections(string[] lines, int start) { Dictionary dictionary = new Dictionary(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 bodyLines) { ParsedSection parsedSection = new ParsedSection(); List list = new List(); 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 output, string name, ParsedSection section) { output.Add("[" + name + "]"); output.Add(string.Empty); HashSet hashSet = new HashSet(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 output, IEnumerable lines) { AppendTrimmed(output, lines); output.Add(string.Empty); } private static void AppendTrimmed(List output, IEnumerable lines) { output.AddRange(TrimBlankLines(lines)); } private static IEnumerable TrimBlankLines(IEnumerable 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 __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 TrackedValues = new Dictionary(); private static readonly List DeadValueKeys = new List(); 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() != (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 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() != (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(); if ((Object)(object)originalLoopSoundReplacement == (Object)null) { originalLoopSoundReplacement = ((Component)original).gameObject.AddComponent(); } 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 MinimumSpawnInLevel { get; private set; } internal ConfigEntry MaximumSpawnInLevel { get; private set; } internal ConfigEntry StoneTakesDamages { get; private set; } internal ConfigEntry StoneStartValue { get; private set; } internal ConfigEntry GoodChance { get; private set; } internal ConfigEntry GoodMultiply { get; private set; } internal ConfigEntry GoodMinimumMultiplier { get; private set; } internal ConfigEntry GoodMaximumMultiplier { get; private set; } internal ConfigEntry GoodIncremental { get; private set; } internal ConfigEntry GoodIncrementalMinimumStart { get; private set; } internal ConfigEntry GoodIncrementalMaximumStart { get; private set; } internal ConfigEntry GoodIncrementalMinimumStep { get; private set; } internal ConfigEntry GoodIncrementalMaximumStep { get; private set; } internal ConfigEntry GoodAddition { get; private set; } internal ConfigEntry GoodMinimumAddition { get; private set; } internal ConfigEntry GoodMaximumAddition { get; private set; } internal ConfigEntry BadBackToStartingValue { get; private set; } internal ConfigEntry BadDivide { get; private set; } internal ConfigEntry BadMinimumDivideBy { get; private set; } internal ConfigEntry BadMaximumDivideBy { get; private set; } internal ConfigEntry BadDecremental { get; private set; } internal ConfigEntry BadDecrementalMinimumStart { get; private set; } internal ConfigEntry BadDecrementalMaximumStart { get; private set; } internal ConfigEntry BadDecrementalMinimumStep { get; private set; } internal ConfigEntry BadDecrementalMaximumStep { get; private set; } internal ConfigEntry BadSubtract { get; private set; } internal ConfigEntry BadMinimumSubtraction { get; private set; } internal ConfigEntry BadMaximumSubtraction { get; private set; } internal ConfigEntry BadResetIncremental { get; private set; } internal ConfigEntry 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 val = ((BaseUnityPlugin)this).Config.Bind("SPAWN", "Minimum Spawn In Level", 0, (ConfigDescription)null); ConfigEntry val2 = ((BaseUnityPlugin)this).Config.Bind("SPAWN", "Maximum Spawn In Level", 2, (ConfigDescription)null); ConfigEntry val3 = ((BaseUnityPlugin)this).Config.Bind("GENERAL", "Item Damages", true, (ConfigDescription)null); ConfigEntry val4 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Multiplier Chance", 80, (ConfigDescription)null); ConfigEntry val5 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Minimum Multiplier", 1f, (ConfigDescription)null); ConfigEntry val6 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Maximum Multiplier", 2f, (ConfigDescription)null); ConfigEntry val7 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Reset Chance", 20, (ConfigDescription)null); ConfigEntry val8 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Toggle Reset", true, (ConfigDescription)null); ConfigEntry val9 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Minimum Divider", 2f, (ConfigDescription)null); ConfigEntry val10 = ((BaseUnityPlugin)this).Config.Bind("CHANCES", "Maximum Divider", 2f, (ConfigDescription)null); ConfigEntry val11 = ((BaseUnityPlugin)this).Config.Bind("COMPATIBILITY", "Fix LevelScaling Level Max Value", true, (ConfigDescription)null); ConfigEntry val12 = ((BaseUnityPlugin)this).Config.Bind("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("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(0, 5), Array.Empty())); MaximumSpawnInLevel = ((BaseUnityPlugin)this).Config.Bind("SPAWN", "Maximum Spawn(s)", value2, new ConfigDescription("Maximum number of Holy Casino Stones selected for each generated level.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 5), Array.Empty())); StoneTakesDamages = ((BaseUnityPlugin)this).Config.Bind("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("STONE", "Stone Start Value", "100", "Dollar value assigned to every Holy Casino Stone when it initializes."); GoodChance = ((BaseUnityPlugin)this).Config.Bind("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(0, 100), Array.Empty())); GoodMultiply = ((BaseUnityPlugin)this).Config.Bind("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("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("GOOD", "Addition", false, "Adds Addition to the random pool of enabled GOOD operations."); GoodMinimumAddition = ((BaseUnityPlugin)this).Config.Bind("GOOD", "Minimum Addition", "100", "Minimum random dollar amount added by Addition."); GoodMaximumAddition = ((BaseUnityPlugin)this).Config.Bind("GOOD", "Maximum Addition", "1000", "Maximum random dollar amount added by Addition. Bounds are reordered when necessary."); BadBackToStartingValue = ((BaseUnityPlugin)this).Config.Bind("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("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("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("BAD", "Subtract", false, "Adds Subtract to the random pool of enabled BAD operations."); BadMinimumSubtraction = ((BaseUnityPlugin)this).Config.Bind("BAD", "Minimum Subtraction", "100", "Minimum random dollar amount removed by Subtract."); BadMaximumSubtraction = ((BaseUnityPlugin)this).Config.Bind("BAD", "Maximum Subtraction", "2000", "Maximum random dollar amount removed by Subtract. Bounds are reordered when necessary."); BadResetIncremental = ((BaseUnityPlugin)this).Config.Bind("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("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 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(section, key, FormatQuarterStep(defaultValue), new ConfigDescription(description + " Selectable from 1 to 10 in 0.25 steps.", (AcceptableValueBase)(object)new AcceptableValueList(CreateQuarterStepOptions()), Array.Empty())); } internal static float ReadQuarterStep(ConfigEntry entry) { return ParseConfigNumber(entry?.Value, 1f); } internal static float ReadNumberInput(ConfigEntry 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 ValuableListFields = new Dictionary(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 valuableListField in ValuableListFields) { if (valuableListField.Value == null || valuableListField.Value.FieldType != typeof(List)) { error = "the ValuableDirector list for " + valuableListField.Key + " was not found as List."; return false; } } error = null; return true; } internal static void BeginLevel() { _casinoPrefab = ((IEnumerable)Valuables.RegisteredValuables).FirstOrDefault((Func)IsCasinoPrefab) ?? ((IEnumerable)Valuables.AllValuables).FirstOrDefault((Func)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 source)) { return null; } List 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)(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 States = new Dictionary(); 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() != (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); } } }