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 ShrinkCart v0.3.1
ShrinkCart.dll
Decompiled 6 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using ScalerCore; using ScalerCore.Handlers; using ShrinkCart.Bootstrap; using ShrinkCart.Config; using ShrinkCart.Core; using ShrinkCart.Domain; using ShrinkCart.Features; using ShrinkCart.Game; using ShrinkCart.Scaling; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ShrinkCart")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.1.0")] [assembly: AssemblyInformationalVersion("0.3.1")] [assembly: AssemblyProduct("ShrinkCart")] [assembly: AssemblyTitle("ShrinkCart")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.1.0")] [module: UnverifiableCode] [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 ShrinkCart { [BepInPlugin("AngelcoMilk.ShrinkCart", "ShrinkCart", "0.3.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private Harmony _harmony; private ShrinkCartRuntime _runtime; private void Awake() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown ModLog.Initialize(((BaseUnityPlugin)this).Logger, () => ModConfig.DebugLoggingEnabled); try { ModConfig.Bind(((BaseUnityPlugin)this).Config); FeatureAvailability featureAvailability = new FeatureAvailability(); int num = GameAccessSelfCheck.Run(featureAvailability); ValuableBoxSupport.RegisterHandler(featureAvailability); _harmony = new Harmony("AngelcoMilk.ShrinkCart"); PatchInstaller.Install(_harmony, typeof(Plugin).Assembly, featureAvailability, out var installed, out var failed); _runtime = new ShrinkCartRuntime(featureAvailability); ShrinkCartRuntime.Current = _runtime; ModLog.Info("ShrinkCart 0.3.1 loaded: patches " + installed + "/" + (installed + failed) + ", missing game members " + num + ", config language " + (ModConfig.IsEnglish ? "English" : "中文") + ". " + featureAvailability.Describe()); } catch (Exception ex) { ShrinkCartRuntime.Current = null; _runtime = null; ModLog.Error("ShrinkCart failed to initialize and stays inactive: " + ex); } } private void Update() { if (_runtime != null) { _runtime.Update(Time.time, Time.unscaledTime); } } private void OnDestroy() { if (_runtime != null) { _runtime.Shutdown(Time.time); _runtime = null; } ShrinkCartRuntime.Current = null; if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "AngelcoMilk.ShrinkCart"; public const string PLUGIN_NAME = "ShrinkCart"; public const string PLUGIN_VERSION = "0.3.1"; } } namespace ShrinkCart.Scaling { internal static class ScaleOptionsFactory { internal static ScaleOptions ForItem(float factor, bool valuableBox) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) ScaleOptions result = ScaleOptions.Default; result.Factor = factor; result.Speed = ModConfig.SafeScaleSpeed(); result.RestoreSpeed = ModConfig.SafeRestoreScaleSpeed(); result.Duration = 0f; result.AllowedTargets = (ScaleTargets)(valuableBox ? 15 : 12); result.SuppressValueDropExpand = ModConfig.SuppressValuableDamageRestore.Value; result.PreserveMass = ModConfig.ShouldPreserveMass(); result.SuppressImpactFlash = true; result.SuppressCameraShake = true; return result; } internal static ScaleOptions ForDeadHead() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) ScaleOptions result = ScaleOptions.Default; result.Factor = ModConfig.SafeDeadHeadScaleFactor(); result.Speed = ModConfig.SafeScaleSpeed(); result.RestoreSpeed = ModConfig.SafeRestoreScaleSpeed(); result.Duration = 0f; result.AllowedTargets = (ScaleTargets)15; result.SuppressValueDropExpand = true; result.PreserveMass = ModConfig.ShouldPreserveMass(); result.SuppressImpactFlash = true; result.SuppressCameraShake = true; result.RejectExternalApply = false; return result; } internal static ScaleOptions ForPlayer(float factor) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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) //IL_0066: Unknown result type (might be due to invalid IL or missing references) ScaleOptions result = ScaleOptions.Default; result.Factor = factor; result.Speed = ModConfig.SafeScaleSpeed(); result.RestoreSpeed = ModConfig.SafeRestoreScaleSpeed(); result.Duration = 0f; result.AllowedTargets = (ScaleTargets)1; result.SuppressImpactFlash = true; result.SuppressCameraShake = true; result.IgnoreBonkExpand = !ModConfig.RestorePlayerOnDamage.Value; result.RejectExternalApply = false; return result; } } internal enum ScaleKind { Item, ValuableBox, DeadHead, Player } internal sealed class ScaleOwnership { internal sealed class OwnedScale { internal GameObject Target { get; } internal ScaleKind Kind { get; } internal float Factor { get; } internal OwnedScale(GameObject target, ScaleKind kind, float factor) { Target = target; Kind = kind; Factor = factor; } } private readonly Dictionary<int, OwnedScale> _owned = new Dictionary<int, OwnedScale>(); internal int Count => _owned.Count; internal void Register(GameObject target, ScaleKind kind, float factor) { _owned[((Object)target).GetInstanceID()] = new OwnedScale(target, kind, factor); } internal bool TryGet(int id, out OwnedScale owned) { return _owned.TryGetValue(id, out owned); } internal bool IsOwned(int id) { return _owned.ContainsKey(id); } internal void Release(int id) { _owned.Remove(id); } internal void Clear() { _owned.Clear(); } } internal enum RestoreResult { Restored, NotOwned, Failed } internal sealed class ScalerCoreGateway { private readonly ScaleOwnership _ownership; internal ScalerCoreGateway(ScaleOwnership ownership) { _ownership = ownership; } internal bool IsScaled(GameObject target) { try { return ScaleManager.IsScaled(target); } catch (Exception ex) { ModLog.WarnOnce("scalercore:isScaled", "ScalerCore IsScaled failed: " + ex.Message); return false; } } internal bool IsOurs(GameObject target) { if ((Object)(object)target == (Object)null || !_ownership.TryGet(((Object)target).GetInstanceID(), out var owned)) { return false; } return MatchesOwned(target, owned); } internal bool TryApply(GameObject target, ScaleOptions options, ScaleKind kind) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) try { bool num = ((kind == ScaleKind.DeadHead) ? ApplyWithDeadHeadsAllowed(target, options) : ScaleManager.ApplyIfNotScaled(target, options)); if (num) { _ownership.Register(target, kind, options.Factor); } return num; } catch (Exception ex) { ModLog.WarnOnce("scalercore:apply:" + kind, "Failed to apply " + kind.ToString() + " scale to " + ((Object)target).name + ": " + ex.Message); return false; } } internal RestoreResult Restore(GameObject target, float restoreSpeed) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return RestoreResult.NotOwned; } int instanceID = ((Object)target).GetInstanceID(); if (!_ownership.TryGet(instanceID, out var owned)) { return RestoreResult.NotOwned; } _ownership.Release(instanceID); try { if (!MatchesOwned(target, owned)) { return RestoreResult.NotOwned; } ScaleOptions currentOptions = ScaleManager.GetController(target).CurrentOptions; currentOptions.RestoreSpeed = restoreSpeed; currentOptions.SuppressImpactFlash = true; currentOptions.SuppressCameraShake = true; if (owned.Kind == ScaleKind.Item || owned.Kind == ScaleKind.ValuableBox) { ScaleManager.UpdateOptions(target, currentOptions); ScaleManager.Restore(target); } else { ScaleManager.ForceUpdateOptions(target, currentOptions); ScaleManager.ForceRestore(target); } return RestoreResult.Restored; } catch (Exception ex) { ModLog.WarnOnce("scalercore:restore:" + owned.Kind, "Failed to restore " + owned.Kind.ToString() + " " + ((Object)target).name + ": " + ex.Message); return RestoreResult.Failed; } } internal void Forget(int id) { _ownership.Release(id); } private static bool MatchesOwned(GameObject target, ScaleOwnership.OwnedScale owned) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) try { ScaleController controller = ScaleManager.GetController(target); return (Object)(object)controller != (Object)null && controller.IsScaled && MathUtil.Approximately(controller.CurrentOptions.Factor, owned.Factor); } catch (Exception ex) { ModLog.WarnOnce("scalercore:controller", "ScalerCore controller query failed: " + ex.Message); return false; } } private static bool ApplyWithDeadHeadsAllowed(GameObject target, ScaleOptions options) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) bool allowDeadHeads = ScaleManager.AllowDeadHeads; try { ScaleManager.AllowDeadHeads = true; return ScaleManager.ApplyIfNotScaled(target, options); } finally { ScaleManager.AllowDeadHeads = allowDeadHeads; ModLog.Debug("ScalerCore AllowDeadHeads restored to " + ScaleManager.AllowDeadHeads + " after death head apply."); } } } internal static class ValuableBoxSupport { private sealed class ValuableBoxHandler : IScaleHandler { public void Setup(ScaleController ctrl) { } public void OnScale(ScaleController ctrl) { } public void OnRestore(ScaleController ctrl, bool isBonk) { } public void OnUpdate(ScaleController ctrl) { } public void OnLateUpdate(ScaleController ctrl) { } public void OnDestroy(ScaleController ctrl) { } } private const int HandlerPriority = -10; private static bool _registered; internal static void RegisterHandler(FeatureAvailability availability) { if (_registered) { return; } try { ScaleHandlerRegistry.Register((IScaleHandler)(object)new ValuableBoxHandler(), (Func<GameObject, bool>)IsValuableBoxScaleTarget, -10); _registered = true; } catch (Exception ex) { string text = "ScalerCore handler registration failed: " + ex.Message; availability.MarkUnavailable(FeatureId.ValuableBoxSupport, text); ModLog.Warn(text); } } internal static void OnPhysGrabObjectStarted(PhysGrabObject item) { if (!((Object)(object)item == (Object)null) && !((Object)(object)SpecialBoxDetector.FindValuableBox(item) == (Object)null)) { EnsureController(((Component)item).gameObject); } } internal static void OnValuableBoxStarted(ItemValuableBox box) { PhysGrabObject val = SpecialBoxDetector.FindOwner(box); if ((Object)(object)val != (Object)null) { EnsureController(((Component)val).gameObject); } } internal static bool EnsureController(GameObject target) { if ((Object)(object)target == (Object)null) { return false; } if ((Object)(object)target.GetComponent<ScaleController>() != (Object)null) { return true; } if ((Object)(object)target.GetComponent<CosmeticWorldObject>() != (Object)null || (Object)(object)target.GetComponentInParent<CosmeticWorldObject>() != (Object)null || (Object)(object)target.GetComponentInChildren<CosmeticWorldObject>(true) != (Object)null) { ModLog.Debug("Cosmetic box is waiting for ScalerCore's cosmetic controller: " + ((Object)target).name); return false; } if (!IsValuableBoxScaleTarget(target)) { return false; } target.AddComponent<ScaleController>(); ModLog.Debug("Attached ScalerCore controller to token box: " + ((Object)target).name); return false; } private static bool IsValuableBoxScaleTarget(GameObject target) { if ((Object)(object)target == (Object)null) { return false; } PhysGrabObject component = target.GetComponent<PhysGrabObject>(); if ((Object)(object)component == (Object)null) { return false; } ItemValuableBox val = SpecialBoxDetector.FindValuableBox(component); if ((Object)(object)val != (Object)null) { return (Object)(object)SpecialBoxDetector.FindOwner(val) == (Object)(object)component; } return false; } } } namespace ShrinkCart.Patches { [PatchFeature(FeatureId.CartObservation, Frequency = "每个物理帧 × 车 trigger 内每个未休眠物体(OnTriggerStay)", Side = "仅主机(原版在 OnTriggerStay 中判定 IsMasterClient)")] [HarmonyPatch(typeof(PhysGrabInCart), "Add")] internal static class PhysGrabInCartAddPatch { private static bool Prefix(PhysGrabInCart __instance, PhysGrabObject _physGrabObject, out bool __state) { __state = false; ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null) { return true; } try { __state = current.Router.ShouldBlockTriggerAdd(__instance, _physGrabObject); } catch (Exception ex) { current.ReportFailure(FeatureId.CartNestingGuard, "PhysGrabInCart.Add prefix", ex); } return !__state; } private static void Postfix(PhysGrabInCart __instance, PhysGrabObject _physGrabObject, bool __state) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (__state || current == null) { return; } try { current.Router.OnTriggerPresence(__instance, _physGrabObject); } catch (Exception ex) { current.ReportFailure(FeatureId.CartObservation, "PhysGrabInCart.Add postfix", ex); } } } [PatchFeature(FeatureId.CartObservation, Frequency = "每辆车生成时一次", Side = "所有端(登记表是被动的,只有主机侧功能使用)")] [HarmonyPatch(typeof(PhysGrabCart), "Start")] internal static class PhysGrabCartStartPatch { private static void Postfix(PhysGrabCart __instance) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null) { return; } try { current.Carts.GetOrRegister(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.CartObservation, "PhysGrabCart.Start postfix", ex); } } } [PatchFeature(FeatureId.CartObservation, Frequency = "每帧 × 每辆车(原版仅在 12 m 内有玩家且计时器到期时每 0.5 秒真正扫描;ShrinkCart 只处理真实扫描)", Side = "所有端调用,仅主机处理")] [HarmonyPatch(typeof(PhysGrabCart), "ObjectsInCart")] internal static class PhysGrabCartObjectsInCartPatch { private static void Prefix(PhysGrabCart __instance, out bool __state) { __state = false; ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null) { return; } try { __state = current.Router.ShouldObserveScan(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.CartObservation, "PhysGrabCart.ObjectsInCart prefix", ex); } } private static void Postfix(PhysGrabCart __instance, bool __state) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (!__state || current == null) { return; } try { current.Router.OnScanMaybeCompleted(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.CartObservation, "PhysGrabCart.ObjectsInCart postfix", ex); } } } [PatchFeature(FeatureId.Core, Frequency = "每次切换关卡一次", Side = "所有端调用;只有主机侧已激活的功能会恢复缩放")] [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class RunManagerChangeLevelPatch { private static void Prefix() { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null) { return; } try { current.OnLevelChanging(Time.time); } catch (Exception ex) { current.ReportFailure(FeatureId.Core, "RunManager.ChangeLevel prefix", ex); } } } [PatchFeature(FeatureId.DeadHeadShrink, Frequency = "每次玩家复活一次", Side = "所有端调用;只有主机侧死亡头颅功能激活时处理")] [HarmonyPatch(typeof(PlayerDeathHead), "Revive")] internal static class PlayerDeathHeadRevivePatch { private static void Prefix(PlayerDeathHead __instance) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null) { return; } try { current.DeadHeads.RestoreBeforeRevive(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.DeadHeadShrink, "PlayerDeathHead.Revive prefix", ex); } } } [PatchFeature(FeatureId.ValuableBoxSupport, Frequency = "每个 PhysGrabObject 生成时一次(非代币箱只做一次组件查找链)", Side = "所有端(客户端也需要控制器接收 ScalerCore 同步)")] [HarmonyPatch(typeof(PhysGrabObject), "Start")] internal static class PhysGrabObjectStartPatch { private static void Postfix(PhysGrabObject __instance) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null || !current.Lifecycle.IsAvailable(FeatureId.ValuableBoxSupport)) { return; } try { ValuableBoxSupport.OnPhysGrabObjectStarted(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.ValuableBoxSupport, "PhysGrabObject.Start postfix", ex); } } } [PatchFeature(FeatureId.ValuableBoxSupport, Frequency = "每个代币箱生成时一次", Side = "所有端(与 PhysGrabObject.Start 互为兜底,取决于两者 Start 的先后顺序)")] [HarmonyPatch(typeof(ItemValuableBox), "Start")] internal static class ItemValuableBoxStartPatch { private static void Postfix(ItemValuableBox __instance) { ShrinkCartRuntime current = ShrinkCartRuntime.Current; if (current == null || !current.Lifecycle.IsAvailable(FeatureId.ValuableBoxSupport)) { return; } try { ValuableBoxSupport.OnValuableBoxStarted(__instance); } catch (Exception ex) { current.ReportFailure(FeatureId.ValuableBoxSupport, "ItemValuableBox.Start postfix", ex); } } } } namespace ShrinkCart.Game { internal static class CartLikeDetector { internal static bool Compute(PhysGrabObject item, string cleanName) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Invalid comparison between Unknown and I4 //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Invalid comparison between Unknown and I4 if (HasOnSelfParentOrChildren<PhysGrabCart>(item) || HasOnSelfParentOrChildren<ItemVehicle>(item) || HasOnSelfParentOrChildren<ItemCartCannon>(item) || HasOnSelfParentOrChildren<ItemCartCannonMain>(item) || HasOnSelfParentOrChildren<ItemCartLaser>(item)) { return true; } if (cleanName == "Item Cart Cannon" || cleanName == "Item Cart Laser") { return true; } ItemAttributes component = ((Component)item).GetComponent<ItemAttributes>(); if ((Object)(object)component == (Object)null) { return false; } itemType val = GameAccess.ItemTypeOf(component); if ((int)val != 2 && (int)val != 14) { return (int)val == 12; } return true; } private static bool HasOnSelfParentOrChildren<T>(PhysGrabObject item) where T : Component { if (!((Object)(object)((Component)item).GetComponent<T>() != (Object)null) && !((Object)(object)((Component)item).GetComponentInParent<T>() != (Object)null)) { return (Object)(object)((Component)item).GetComponentInChildren<T>(true) != (Object)null; } return true; } } internal sealed class CartRegistry { internal sealed class CartInfo { internal Transform InCartVolume; internal PhysGrabCart Cart { get; } internal int Id { get; } internal bool IsSmall { get; } internal CartInfo(PhysGrabCart cart, int id) { Cart = cart; Id = id; IsSmall = cart.isSmallCart; } } private readonly Dictionary<int, CartInfo> _carts = new Dictionary<int, CartInfo>(); private readonly List<int> _removeIds = new List<int>(8); internal Dictionary<int, CartInfo>.ValueCollection All => _carts.Values; internal int Count => _carts.Count; internal event Action<int, float> CartRemoved; internal CartInfo GetOrRegister(PhysGrabCart cart) { if ((Object)(object)cart == (Object)null) { return null; } int instanceID = ((Object)cart).GetInstanceID(); if (!_carts.TryGetValue(instanceID, out var value)) { value = new CartInfo(cart, instanceID); _carts[instanceID] = value; ModLog.Debug("Registered cart: " + ((Object)cart).name + (value.IsSmall ? " (small)" : string.Empty)); } return value; } internal void RegisterExisting() { PhysGrabCart[] array = Object.FindObjectsOfType<PhysGrabCart>(); for (int i = 0; i < array.Length; i++) { GetOrRegister(array[i]); } } internal void Prune(float now) { _removeIds.Clear(); foreach (KeyValuePair<int, CartInfo> cart in _carts) { if ((Object)(object)cart.Value.Cart == (Object)null) { _removeIds.Add(cart.Key); } } for (int i = 0; i < _removeIds.Count; i++) { _carts.Remove(_removeIds[i]); this.CartRemoved?.Invoke(_removeIds[i], now); } } internal void Clear() { _carts.Clear(); } } internal static class GameAccess { [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "itemsInCart", typeof(List<PhysGrabObject>), new FeatureId[] { FeatureId.CartObservation })] internal static List<PhysGrabObject> ItemsInCart(PhysGrabCart cart) { return cart.itemsInCart; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "objectInCartCheckTimer", typeof(float), new FeatureId[] { FeatureId.CartObservation })] internal static float CartCheckTimer(PhysGrabCart cart) { return cart.objectInCartCheckTimer; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(ItemAttributes), "itemType", typeof(itemType), new FeatureId[] { FeatureId.CartObservation })] internal static itemType ItemTypeOf(ItemAttributes attributes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return attributes.itemType; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabObject), "isGun", typeof(bool), new FeatureId[] { FeatureId.CartObservation })] internal static bool IsGun(PhysGrabObject item) { return item.isGun; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "itemsInCartCount", typeof(int), new FeatureId[] { FeatureId.CartNestingGuard })] internal static int ItemsInCartCount(PhysGrabCart cart) { return cart.itemsInCartCount; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "itemsInCartCount", typeof(int), new FeatureId[] { FeatureId.CartNestingGuard })] internal static void SetItemsInCartCount(PhysGrabCart cart, int value) { cart.itemsInCartCount = value; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "haulCurrent", typeof(int), new FeatureId[] { FeatureId.CartNestingGuard })] internal static int HaulCurrent(PhysGrabCart cart) { return cart.haulCurrent; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "haulCurrent", typeof(int), new FeatureId[] { FeatureId.CartNestingGuard })] internal static void SetHaulCurrent(PhysGrabCart cart, int value) { cart.haulCurrent = value; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(ValuableObject), "dollarValueCurrent", typeof(float), new FeatureId[] { FeatureId.CartNestingGuard })] internal static float DollarValueCurrent(ValuableObject valuable) { return valuable.dollarValueCurrent; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(Enemy), "Health", typeof(EnemyHealth), new FeatureId[] { FeatureId.EnemyCrush })] internal static EnemyHealth HealthOf(Enemy enemy) { return enemy.Health; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(EnemyHealth), "healthCurrent", typeof(int), new FeatureId[] { FeatureId.EnemyCrush })] internal static int CurrentHealth(EnemyHealth health) { return health.healthCurrent; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(EnemyHealth), "dead", typeof(bool), new FeatureId[] { FeatureId.EnemyCrush })] internal static bool IsDead(EnemyHealth health) { return health.dead; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PlayerDeathHead), "physGrabObject", typeof(PhysGrabObject), new FeatureId[] { FeatureId.DeadHeadShrink })] internal static PhysGrabObject HeadPhysGrabObject(PlayerDeathHead head) { return head.physGrabObject; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PlayerDeathHead), "triggered", typeof(bool), new FeatureId[] { FeatureId.DeadHeadShrink })] internal static bool IsHeadTriggered(PlayerDeathHead head) { return head.triggered; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PlayerDeathHead), "inExtractionPoint", typeof(bool), new FeatureId[] { FeatureId.DeadHeadShrink })] internal static bool IsHeadInExtractionPoint(PlayerDeathHead head) { return head.inExtractionPoint; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PlayerDeathHead), "inTruck", typeof(bool), new FeatureId[] { FeatureId.DeadHeadShrink })] internal static bool IsHeadInTruck(PlayerDeathHead head) { return head.inTruck; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PhysGrabCart), "inCart", typeof(Transform), new FeatureId[] { FeatureId.PlayerScaling })] internal static Transform InCartVolume(PhysGrabCart cart) { return cart.inCart; } [MethodImpl(MethodImplOptions.NoInlining)] [GameMember(typeof(PlayerAvatar), "collider", typeof(Collider), new FeatureId[] { FeatureId.PlayerScaling })] internal static Collider AvatarCollider(PlayerAvatar avatar) { return avatar.collider; } } internal static class GameAccessSelfCheck { private const BindingFlags AllInstanceAndStatic = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static int Run(FeatureAvailability availability) { HashSet<string> hashSet = new HashSet<string>(); MethodInfo[] methods = typeof(GameAccess).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < methods.Length; i++) { object[] customAttributes = methods[i].GetCustomAttributes(typeof(GameMemberAttribute), inherit: false); for (int j = 0; j < customAttributes.Length; j++) { GameMemberAttribute gameMemberAttribute = (GameMemberAttribute)customAttributes[j]; string text = Check(gameMemberAttribute); if (text != null) { for (int k = 0; k < gameMemberAttribute.Features.Length; k++) { availability.MarkUnavailable(gameMemberAttribute.Features[k], text); } if (hashSet.Add(text)) { ModLog.Warn(text); } } } } return hashSet.Count; } private static string Check(GameMemberAttribute member) { string text = member.Owner.Name + "." + member.Name; try { FieldInfo field = member.Owner.GetField(member.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return (member.MemberType == null || field.FieldType == member.MemberType) ? null : ("Game member type changed: " + text + " is " + field.FieldType.Name + ", expected " + member.MemberType.Name); } if (member.Owner.GetProperty(member.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { return null; } MethodInfo[] methods = member.Owner.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < methods.Length; i++) { if (methods[i].Name == member.Name) { return null; } } return "Game member missing: " + text; } catch (Exception ex) { return "Game member check failed for " + text + ": " + ex.Message; } } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] internal sealed class GameMemberAttribute : Attribute { internal Type Owner { get; } internal string Name { get; } internal Type MemberType { get; } internal FeatureId[] Features { get; } internal GameMemberAttribute(Type owner, string name, Type memberType, params FeatureId[] features) { Owner = owner; Name = name; MemberType = memberType; Features = features; } } internal enum ObjectKind { Unsupported, CartLike, DeathHead, Enemy, ShrinkCandidate } internal sealed class ObjectInfo { internal int Id; internal ObjectKind Kind; internal string CleanName; internal Enemy Enemy; internal PlayerDeathHead Head; internal ItemEquippable Equippable; internal ShrinkCategory Category; internal bool IsShopItem; internal int ShrinkConfigVersion = -1; internal bool CanShrink; internal float Factor = 1f; } internal sealed class ObjectClassifier { private readonly Dictionary<int, ObjectInfo> _cache = new Dictionary<int, ObjectInfo>(); internal int Count => _cache.Count; internal ObjectInfo Get(PhysGrabObject item) { int instanceID = ((Object)((Component)item).gameObject).GetInstanceID(); if (_cache.TryGetValue(instanceID, out var value)) { return value; } value = Classify(item, instanceID); _cache[instanceID] = value; DebugCounters.Classifications++; return value; } internal bool TryGetShrinkFactor(ObjectInfo info, out float factor) { if (info.Kind != ObjectKind.ShrinkCandidate) { factor = 1f; return false; } int scalingConfigVersion = ModConfig.ScalingConfigVersion; if (info.ShrinkConfigVersion != scalingConfigVersion) { float factor2 = 1f; info.CanShrink = ((!info.IsShopItem) ? ModConfig.TryGetScaleFactor(info.Category, out factor2) : (ModConfig.ShrinkShopPlayerItems.Value && ModConfig.TryGetScaleFactor(ShrinkCategory.Fallback, out factor2))); info.Factor = (info.CanShrink ? factor2 : 1f); info.ShrinkConfigVersion = scalingConfigVersion; } factor = info.Factor; return info.CanShrink; } internal void Clear() { _cache.Clear(); } private static ObjectInfo Classify(PhysGrabObject item, int id) { ObjectInfo objectInfo = new ObjectInfo { Id = id, Kind = ObjectKind.Unsupported, CleanName = ObjectNames.Clean(((Object)item).name) }; if (CartLikeDetector.Compute(item, objectInfo.CleanName)) { objectInfo.Kind = ObjectKind.CartLike; return objectInfo; } PlayerDeathHead val = FindDeathHead(item); if ((Object)(object)val != (Object)null) { objectInfo.Kind = ObjectKind.DeathHead; objectInfo.Head = val; return objectInfo; } Enemy val2 = FindEnemy(item); if ((Object)(object)val2 != (Object)null) { objectInfo.Kind = ObjectKind.Enemy; objectInfo.Enemy = val2; return objectInfo; } objectInfo.Equippable = ((Component)item).GetComponent<ItemEquippable>(); if (TryResolveCategory(item, objectInfo.CleanName, out var category)) { objectInfo.Kind = ObjectKind.ShrinkCandidate; objectInfo.Category = category; if (category == ShrinkCategory.ValuableBox) { ModLog.Debug("Resolved token/cosmetic box category for " + ((Object)item).name + " kind=" + SpecialBoxDetector.Describe(item)); } return objectInfo; } if (IsShopItem(item, objectInfo.Equippable)) { objectInfo.Kind = ObjectKind.ShrinkCandidate; objectInfo.Category = ShrinkCategory.Fallback; objectInfo.IsShopItem = true; } return objectInfo; } private static bool TryResolveCategory(PhysGrabObject item, string cleanName, out ShrinkCategory category) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) category = ShrinkCategory.Fallback; if ((Object)(object)((Component)item).GetComponent<SurplusValuable>() != (Object)null) { category = ShrinkCategory.Surplus; return true; } if (SpecialBoxDetector.IsSpecialBox(item)) { category = ShrinkCategory.ValuableBox; return true; } if (ObjectNames.TryParseEnemyOrb(cleanName, out category)) { return true; } ValuableObject component = ((Component)item).GetComponent<ValuableObject>(); if ((Object)(object)component == (Object)null) { return false; } category = FromVolumeType(component.volumeType); return true; } private static bool IsShopItem(PhysGrabObject item, ItemEquippable equippable) { if (!((Object)(object)((Component)item).GetComponent<ItemAttributes>() != (Object)null) && !((Object)(object)equippable != (Object)null) && !GameAccess.IsGun(item)) { return (Object)(object)((Component)item).GetComponent<ItemBattery>() != (Object)null; } return true; } private static ShrinkCategory FromVolumeType(Type volumeType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected I4, but got Unknown return (int)volumeType switch { 0 => ShrinkCategory.Tiny, 1 => ShrinkCategory.Small, 2 => ShrinkCategory.Medium, 3 => ShrinkCategory.Big, 4 => ShrinkCategory.Wide, 5 => ShrinkCategory.Tall, 6 => ShrinkCategory.VeryTall, _ => ShrinkCategory.Fallback, }; } private static PlayerDeathHead FindDeathHead(PhysGrabObject item) { PlayerDeathHead component = ((Component)item).GetComponent<PlayerDeathHead>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)item).GetComponentInParent<PlayerDeathHead>(); if (!((Object)(object)component != (Object)null)) { return ((Component)item).GetComponentInChildren<PlayerDeathHead>(true); } return component; } private static Enemy FindEnemy(PhysGrabObject item) { EnemyRigidbody componentInParent = ((Component)item).GetComponentInParent<EnemyRigidbody>(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.enemy != (Object)null) { return componentInParent.enemy; } return ((Component)item).GetComponentInParent<Enemy>(); } } internal static class SpecialBoxDetector { internal static bool IsSpecialBox(PhysGrabObject item) { if (!((Object)(object)FindCosmeticWorldObject(item) != (Object)null)) { return (Object)(object)FindValuableBox(item) != (Object)null; } return true; } internal static string Describe(PhysGrabObject item) { if ((Object)(object)FindCosmeticWorldObject(item) != (Object)null) { return "CosmeticWorldObject"; } if (!((Object)(object)FindValuableBox(item) != (Object)null)) { return "none"; } return "ItemValuableBox"; } internal static CosmeticWorldObject FindCosmeticWorldObject(PhysGrabObject item) { if ((Object)(object)item == (Object)null) { return null; } CosmeticWorldObject component = ((Component)item).GetComponent<CosmeticWorldObject>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)item).GetComponentInParent<CosmeticWorldObject>(); if (!((Object)(object)component != (Object)null)) { return ((Component)item).GetComponentInChildren<CosmeticWorldObject>(true); } return component; } internal static ItemValuableBox FindValuableBox(PhysGrabObject item) { if ((Object)(object)item == (Object)null) { return null; } ItemValuableBox component = ((Component)item).GetComponent<ItemValuableBox>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)item).GetComponentInParent<ItemValuableBox>(); if (!((Object)(object)component != (Object)null)) { return ((Component)item).GetComponentInChildren<ItemValuableBox>(true); } return component; } internal static PhysGrabObject FindOwner(ItemValuableBox box) { if ((Object)(object)box == (Object)null) { return null; } PhysGrabObject component = ((Component)box).GetComponent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)box).GetComponentInParent<PhysGrabObject>(); if (!((Object)(object)component != (Object)null)) { return ((Component)box).GetComponentInChildren<PhysGrabObject>(true); } return component; } } } namespace ShrinkCart.Features { internal sealed class CartNestingGuardFeature : IFeature { private readonly ObjectClassifier _classifier; private bool _active; public FeatureId Id => FeatureId.CartNestingGuard; public bool IsConfiguredEnabled => ModConfig.CartNestingGuardEnabledValue(); internal bool IsActive => _active; internal CartNestingGuardFeature(ObjectClassifier classifier) { _classifier = classifier; } public void Activate(float now) { _active = true; } public void Deactivate(DeactivateReason reason, float now) { _active = false; } public void ResetForLevel(float now) { } public void Tick(float now) { } internal bool ShouldBlock(PhysGrabObject item) { if (_active && (Object)(object)item != (Object)null) { return _classifier.Get(item).Kind == ObjectKind.CartLike; } return false; } internal void CleanScanResult(PhysGrabCart cart, List<PhysGrabObject> items) { if (!_active) { return; } int num = 0; int num2 = 0; for (int num3 = items.Count - 1; num3 >= 0; num3--) { PhysGrabObject val = items[num3]; if (!((Object)(object)val == (Object)null) && _classifier.Get(val).Kind == ObjectKind.CartLike) { ValuableObject componentInParent = ((Component)val).GetComponentInParent<ValuableObject>(); if ((Object)(object)componentInParent != (Object)null) { num2 += (int)GameAccess.DollarValueCurrent(componentInParent); } items.RemoveAt(num3); num++; } } if (num != 0) { GameAccess.SetItemsInCartCount(cart, Math.Max(0, GameAccess.ItemsInCartCount(cart) - num)); int num4 = Math.Max(0, GameAccess.HaulCurrent(cart) - num2); GameAccess.SetHaulCurrent(cart, num4); if ((Object)(object)cart.valueScreen != (Object)null) { cart.valueScreen.UpdateValue(num4); } ModLog.Debug("Removed " + num + " cart-like object(s) from cart contents: " + ((Object)cart).name); } } } internal sealed class CartObservationRouter { private readonly ModLifecycle _lifecycle; private readonly CartRegistry _carts; private readonly ObjectClassifier _classifier; private readonly ItemShrinkFeature _items; private readonly CartNestingGuardFeature _nesting; private readonly EnemyCrushFeature _enemies; private readonly DeadHeadShrinkFeature _heads; private bool AnyCartFeatureActive { get { if (!_lifecycle.IsActive(FeatureId.ItemShrink) && !_lifecycle.IsActive(FeatureId.CartNestingGuard) && !_lifecycle.IsActive(FeatureId.EnemyCrush)) { return _lifecycle.IsActive(FeatureId.DeadHeadShrink); } return true; } } internal CartObservationRouter(ModLifecycle lifecycle, CartRegistry carts, ObjectClassifier classifier, ItemShrinkFeature items, CartNestingGuardFeature nesting, EnemyCrushFeature enemies, DeadHeadShrinkFeature heads) { _lifecycle = lifecycle; _carts = carts; _classifier = classifier; _items = items; _nesting = nesting; _enemies = enemies; _heads = heads; } internal bool ShouldBlockTriggerAdd(PhysGrabInCart inCart, PhysGrabObject item) { if ((Object)(object)inCart != (Object)null && (Object)(object)inCart.cart != (Object)null) { return _nesting.ShouldBlock(item); } return false; } internal void OnTriggerPresence(PhysGrabInCart inCart, PhysGrabObject item) { DebugCounters.TriggerCalls++; if ((Object)(object)inCart == (Object)null || (Object)(object)item == (Object)null || !AnyCartFeatureActive) { return; } PhysGrabCart cart = inCart.cart; if (!((Object)(object)cart == (Object)null)) { float time = Time.time; int instanceID = ((Object)cart).GetInstanceID(); if (_items.TryMarkSeen(((Object)((Component)item).gameObject).GetInstanceID(), instanceID, time)) { DebugCounters.TriggerFastPath++; } else { Dispatch(item, _classifier.Get(item), instanceID, time, fromScan: false); } } } internal bool ShouldObserveScan(PhysGrabCart cart) { if ((Object)(object)cart != (Object)null && AnyCartFeatureActive) { return GameAccess.CartCheckTimer(cart) <= 0f; } return false; } internal void OnScanMaybeCompleted(PhysGrabCart cart) { if (GameAccess.CartCheckTimer(cart) <= 0f) { return; } List<PhysGrabObject> list = GameAccess.ItemsInCart(cart); if (list == null) { return; } DebugCounters.ScansProcessed++; float time = Time.time; CartRegistry.CartInfo orRegister = _carts.GetOrRegister(cart); try { _nesting.CleanScanResult(cart, list); } catch (Exception ex) { _lifecycle.ReportFailure(FeatureId.CartNestingGuard, "scan cleanup", ex); } _items.BeginScan(orRegister.Id); _heads.BeginScan(orRegister.Id); for (int i = 0; i < list.Count; i++) { PhysGrabObject val = list[i]; if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)((Component)val).gameObject).GetInstanceID(); if (!_items.TryMarkSeen(instanceID, orRegister.Id, time) && !_heads.TryMarkSeen(instanceID, orRegister.Id, time)) { Dispatch(val, _classifier.Get(val), orRegister.Id, time, fromScan: true); } } } _items.EndScan(orRegister.Id, time); _heads.EndScan(orRegister.Id, time); } private void Dispatch(PhysGrabObject item, ObjectInfo info, int cartId, float now, bool fromScan) { switch (info.Kind) { case ObjectKind.Enemy: try { _enemies.OnEnemyInCart(info, now); break; } catch (Exception ex3) { _lifecycle.ReportFailure(FeatureId.EnemyCrush, "enemy in cart", ex3); break; } case ObjectKind.DeathHead: if (fromScan) { try { _heads.OnHeadInCart(item, info, cartId, now); break; } catch (Exception ex2) { _lifecycle.ReportFailure(FeatureId.DeadHeadShrink, "death head in cart", ex2); break; } } break; case ObjectKind.ShrinkCandidate: try { _items.OnCandidateInCart(item, info, cartId, now); break; } catch (Exception ex) { _lifecycle.ReportFailure(FeatureId.ItemShrink, "item in cart", ex); break; } } } } internal sealed class DeadHeadShrinkFeature : IFeature { private sealed class TrackedHead { internal GameObject Target { get; } internal PlayerDeathHead Head { get; } internal TrackedHead(GameObject target, PlayerDeathHead head) { Target = target; Head = head; } } private readonly CartRegistry _carts; private readonly ScalerCoreGateway _scaling; private readonly PresenceTracker _presence = new PresenceTracker(); private readonly Dictionary<int, TrackedHead> _heads = new Dictionary<int, TrackedHead>(); private readonly List<int> _ids = new List<int>(8); private readonly List<int> _due = new List<int>(8); private float _nextMaintenance; private bool _active; public FeatureId Id => FeatureId.DeadHeadShrink; public bool IsConfiguredEnabled => ModConfig.DeadHeadScalingEnabledValue(); internal DeadHeadShrinkFeature(CartRegistry carts, ScalerCoreGateway scaling) { _carts = carts; _scaling = scaling; } public void Activate(float now) { _active = true; _nextMaintenance = now; _carts.CartRemoved += OnCartRemoved; } public void Deactivate(DeactivateReason reason, float now) { _carts.CartRemoved -= OnCartRemoved; if (reason == DeactivateReason.AuthorityLost) { ForgetAll(); } else { RestoreAll("deactivate " + reason); } _active = false; } public void ResetForLevel(float now) { RestoreAll("level change"); } internal bool TryMarkSeen(int id, int cartId, float now) { if (_active) { return _presence.TryMarkSeen(id, cartId, now); } return false; } internal void BeginScan(int cartId) { if (_active) { _presence.BeginScan(cartId); } } internal void EndScan(int cartId, float now) { if (_active) { _presence.EndScan(cartId, now); } } internal void OnHeadInCart(PhysGrabObject item, ObjectInfo info, int cartId, float now) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (!_active) { return; } PlayerDeathHead head = info.Head; if ((Object)(object)head == (Object)null || !GameAccess.IsHeadTriggered(head) || (Object)(object)GameAccess.HeadPhysGrabObject(head) != (Object)(object)item) { return; } GameObject gameObject = ((Component)item).gameObject; if (_scaling.IsScaled(gameObject)) { ModLog.Debug("Skipped death head cart shrink because another scale session is active: " + ((Object)gameObject).name); return; } ScaleOptions options = ScaleOptionsFactory.ForDeadHead(); if (!_scaling.TryApply(gameObject, options, ScaleKind.DeadHead)) { ModLog.Debug("ScalerCore rejected death head cart shrink: " + ((Object)gameObject).name); return; } bool num = _heads.Count == 0; _heads[info.Id] = new TrackedHead(gameObject, head); _presence.Track(info.Id, cartId, now); if (num) { _nextMaintenance = Math.Min(_nextMaintenance, now + CurrentInterval()); } ModLog.Debug("Shrunk death head in cart: " + ((Object)gameObject).name + " factor=" + options.Factor.ToString("0.###")); } internal void RestoreBeforeRevive(PlayerDeathHead head) { if (!_active || (Object)(object)head == (Object)null) { return; } PhysGrabObject val = GameAccess.HeadPhysGrabObject(head); if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)((Component)val).gameObject).GetInstanceID(); if (_heads.ContainsKey(instanceID)) { Restore(instanceID, "before revive"); } } } public void Tick(float now) { float debounceSeconds = ModConfig.SafeCartLeaveDebounceSeconds(); if (_presence.HasDue(now, debounceSeconds)) { _presence.CollectDue(now, debounceSeconds, _due); for (int i = 0; i < _due.Count; i++) { Restore(_due[i], "left cart"); } } if (now < _nextMaintenance) { return; } _nextMaintenance = now + CurrentInterval(); if (_heads.Count == 0) { return; } _ids.Clear(); foreach (int key in _heads.Keys) { _ids.Add(key); } for (int j = 0; j < _ids.Count; j++) { int num = _ids[j]; TrackedHead trackedHead = _heads[num]; if ((Object)(object)trackedHead.Target == (Object)null || (Object)(object)trackedHead.Head == (Object)null) { Drop(num); } else if (!_scaling.IsOurs(trackedHead.Target)) { ModLog.Debug("Death head scale was released outside ShrinkCart: " + ((Object)trackedHead.Target).name); Drop(num); } else if (!GameAccess.IsHeadTriggered(trackedHead.Head) || GameAccess.IsHeadInExtractionPoint(trackedHead.Head) || GameAccess.IsHeadInTruck(trackedHead.Head)) { Restore(num, "extraction/truck/untriggered"); } } } private float CurrentInterval() { return ScanCadence.Interval(ModConfig.DynamicItemScanEnabledValue(), _heads.Count, ModConfig.SafeMinimumItemScanIntervalSeconds(), ModConfig.SafeMaximumItemScanIntervalSeconds()); } private void Restore(int id, string reason) { _presence.Remove(id); if (_heads.TryGetValue(id, out var value)) { _heads.Remove(id); if ((Object)(object)value.Target == (Object)null) { _scaling.Forget(id); } else if (_scaling.Restore(value.Target, ModConfig.SafeRestoreScaleSpeed()) == RestoreResult.Restored) { ModLog.Debug("Restored death head " + ((Object)value.Target).name + " reason=" + reason); } } } private void Drop(int id) { _heads.Remove(id); _presence.Remove(id); _scaling.Forget(id); } private void RestoreAll(string reason) { _ids.Clear(); foreach (int key in _heads.Keys) { _ids.Add(key); } for (int i = 0; i < _ids.Count; i++) { Restore(_ids[i], reason); } _heads.Clear(); _presence.Clear(); } private void ForgetAll() { foreach (int key in _heads.Keys) { _scaling.Forget(key); } _heads.Clear(); _presence.Clear(); } private void OnCartRemoved(int cartId, float now) { _presence.OnCartRemoved(cartId, now); } } internal sealed class EnemyCrushFeature : IFeature { private const float KillRetryCooldownSeconds = 1f; private const float PruneIntervalSeconds = 5f; private readonly RetryCooldown _retry = new RetryCooldown(); private float _nextPrune; private bool _active; public FeatureId Id => FeatureId.EnemyCrush; public bool IsConfiguredEnabled => ModConfig.EnemyInCartInstantKill.Value; public void Activate(float now) { _active = true; } public void Deactivate(DeactivateReason reason, float now) { _active = false; _retry.Clear(); } public void ResetForLevel(float now) { _retry.Clear(); } public void Tick(float now) { if (!(now < _nextPrune)) { _nextPrune = now + 5f; _retry.Prune(now); } } internal void OnEnemyInCart(ObjectInfo info, float now) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (!_active) { return; } Enemy enemy = info.Enemy; if ((Object)(object)enemy == (Object)null) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (_retry.IsCooling(instanceID, now)) { return; } EnemyHealth val = GameAccess.HealthOf(enemy); if ((Object)(object)val == (Object)null) { return; } int num = GameAccess.CurrentHealth(val); if (GameAccess.IsDead(val) || num <= 0) { return; } _retry.Start(instanceID, now, 1f); try { val.Hurt(Math.Max(num, val.health) + 1, Vector3.up); ModLog.Debug("Instant killed enemy entering cart: " + ((Object)enemy).name); } catch (Exception ex) { _retry.Cancel(instanceID); ModLog.WarnOnce("enemy-crush", "Failed to instant kill enemy entering cart: " + ex.Message); } } } internal sealed class ItemShrinkFeature : IFeature { private sealed class TrackedItem { internal GameObject Target { get; } internal ItemEquippable Equippable { get; } internal TrackedItem(GameObject target, ItemEquippable equippable) { Target = target; Equippable = equippable; } } private readonly CartRegistry _carts; private readonly ObjectClassifier _classifier; private readonly ScalerCoreGateway _scaling; private readonly PresenceTracker _presence = new PresenceTracker(); private readonly RetryCooldown _reshrinkCooldown = new RetryCooldown(); private readonly Dictionary<int, TrackedItem> _items = new Dictionary<int, TrackedItem>(); private readonly List<int> _ids = new List<int>(16); private readonly List<int> _due = new List<int>(16); private float _nextMaintenance; private bool _active; public FeatureId Id => FeatureId.ItemShrink; public bool IsConfiguredEnabled => ModConfig.CartShrinkingEnabled.Value; internal int TrackedCount => _items.Count; internal ItemShrinkFeature(CartRegistry carts, ObjectClassifier classifier, ScalerCoreGateway scaling) { _carts = carts; _classifier = classifier; _scaling = scaling; } public void Activate(float now) { _active = true; _nextMaintenance = now; _carts.CartRemoved += OnCartRemoved; } public void Deactivate(DeactivateReason reason, float now) { _carts.CartRemoved -= OnCartRemoved; if (reason == DeactivateReason.AuthorityLost) { ForgetAll(); } else { RestoreAll(now); } _reshrinkCooldown.Clear(); _active = false; } public void ResetForLevel(float now) { RestoreAll(now); _reshrinkCooldown.Clear(); } internal bool TryMarkSeen(int id, int cartId, float now) { if (_active) { return _presence.TryMarkSeen(id, cartId, now); } return false; } internal void BeginScan(int cartId) { if (_active) { _presence.BeginScan(cartId); } } internal void EndScan(int cartId, float now) { if (_active) { _presence.EndScan(cartId, now); } } internal void OnCandidateInCart(PhysGrabObject item, ObjectInfo info, int cartId, float now) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (!_active || !_classifier.TryGetShrinkFactor(info, out var factor) || item.dead || ((Object)(object)info.Equippable != (Object)null && info.Equippable.IsEquipped()) || ((Object)(object)item.rb != (Object)null && item.rb.isKinematic) || _reshrinkCooldown.IsCooling(info.Id, now)) { return; } GameObject gameObject = ((Component)item).gameObject; if (_scaling.IsScaled(gameObject)) { return; } bool flag = info.Category == ShrinkCategory.ValuableBox; if (flag && !ValuableBoxSupport.EnsureController(gameObject)) { ModLog.Debug("Token/cosmetic box controller not ready, retrying on next observation: " + ((Object)gameObject).name + " kind=" + SpecialBoxDetector.Describe(item)); return; } ScaleOptions options = ScaleOptionsFactory.ForItem(factor, flag); if (!_scaling.TryApply(gameObject, options, flag ? ScaleKind.ValuableBox : ScaleKind.Item)) { if (flag) { ModLog.Debug("ScalerCore rejected token/cosmetic box shrink: " + ((Object)gameObject).name + " kind=" + SpecialBoxDetector.Describe(item)); } return; } bool num = _items.Count == 0; _items[info.Id] = new TrackedItem(gameObject, info.Equippable); _presence.Track(info.Id, cartId, now); if (num) { _nextMaintenance = Math.Min(_nextMaintenance, now + CurrentInterval()); } ModLog.Debug("Shrunk " + ((Object)gameObject).name + " as " + info.Category.ToString() + " factor=" + factor.ToString("0.###")); } public void Tick(float now) { float debounceSeconds = ModConfig.SafeCartLeaveDebounceSeconds(); if (_presence.HasDue(now, debounceSeconds)) { _presence.CollectDue(now, debounceSeconds, _due); for (int i = 0; i < _due.Count; i++) { Restore(_due[i], now); } } if (now < _nextMaintenance) { return; } _nextMaintenance = now + CurrentInterval(); _reshrinkCooldown.Prune(now); if (_items.Count == 0) { return; } _ids.Clear(); foreach (int key in _items.Keys) { _ids.Add(key); } for (int j = 0; j < _ids.Count; j++) { int num = _ids[j]; TrackedItem trackedItem = _items[num]; if ((Object)(object)trackedItem.Target == (Object)null) { Drop(num); } else if (!_scaling.IsOurs(trackedItem.Target)) { ModLog.Debug("Scale was released outside ShrinkCart, re-shrink allowed after cooldown: " + ((Object)trackedItem.Target).name); Drop(num); _reshrinkCooldown.Start(num, now, ModConfig.SafeReshrinkCooldownSeconds()); } else if ((Object)(object)trackedItem.Equippable != (Object)null && trackedItem.Equippable.IsEquipped()) { ModLog.Debug("Restoring tracked equippable before equipped use: " + ((Object)trackedItem.Target).name); Restore(num, now); } } } private float CurrentInterval() { return ScanCadence.Interval(ModConfig.DynamicItemScanEnabledValue(), _items.Count, ModConfig.SafeMinimumItemScanIntervalSeconds(), ModConfig.SafeMaximumItemScanIntervalSeconds()); } private void Restore(int id, float now) { _presence.Remove(id); if (_items.TryGetValue(id, out var value)) { _items.Remove(id); _reshrinkCooldown.Start(id, now, ModConfig.SafeReshrinkCooldownSeconds()); if ((Object)(object)value.Target == (Object)null) { _scaling.Forget(id); } else if (_scaling.Restore(value.Target, ModConfig.SafeRestoreScaleSpeed()) == RestoreResult.Restored) { ModLog.Debug("Restored " + ((Object)value.Target).name + " speed=" + ModConfig.SafeRestoreScaleSpeed().ToString("0.###")); } } } private void Drop(int id) { _items.Remove(id); _presence.Remove(id); _scaling.Forget(id); } private void RestoreAll(float now) { _ids.Clear(); foreach (int key in _items.Keys) { _ids.Add(key); } for (int i = 0; i < _ids.Count; i++) { Restore(_ids[i], now); } _items.Clear(); _presence.Clear(); } private void ForgetAll() { foreach (int key in _items.Keys) { _scaling.Forget(key); } _items.Clear(); _presence.Clear(); } private void OnCartRemoved(int cartId, float now) { _presence.OnCartRemoved(cartId, now); } } internal sealed class PlayerScalingFeature : IFeature { private sealed class PlayerEntry { internal PlayerAvatar Player { get; } internal int TargetId { get; } internal PlayerCycle Cycle { get; } = new PlayerCycle(); internal PlayerEntry(PlayerAvatar player) { Player = player; TargetId = ((Object)((Component)player).gameObject).GetInstanceID(); } } private const float CenterZoneHorizontalScale = 0.45f; private const float FloorProjectionPaddingBelow = 0.25f; private const float FloorProjectionStandingHeightAbove = 1.4f; private const float MinimumCenterHalfExtent = 0.15f; private const float StandPointYOffset = 0.05f; private readonly CartRegistry _carts; private readonly ScalerCoreGateway _scaling; private readonly Dictionary<int, PlayerEntry> _players = new Dictionary<int, PlayerEntry>(); private readonly List<int> _removeIds = new List<int>(8); private float _nextDetection; public FeatureId Id => FeatureId.PlayerScaling; public bool IsConfiguredEnabled => ModConfig.PlayerScalingEnabled(); internal PlayerScalingFeature(CartRegistry carts, ScalerCoreGateway scaling) { _carts = carts; _scaling = scaling; } public void Activate(float now) { _nextDetection = now; } public void Deactivate(DeactivateReason reason, float now) { if (reason == DeactivateReason.AuthorityLost) { ForgetAll(); } else { RestoreAll(); } } public void ResetForLevel(float now) { RestoreAll(); } public void Tick(float now) { if (now < _nextDetection) { return; } _nextDetection = now + ModConfig.SafePlayerCartDetectionIntervalSeconds(); List<PlayerAvatar> players = GetPlayers(); if (players != null) { float exitGrace = ModConfig.SafePlayerCartExitGraceSeconds(); float standTrigger = ModConfig.SafePlayerCartStandTriggerSeconds(); for (int i = 0; i < players.Count; i++) { ProcessPlayer(players[i], now, exitGrace, standTrigger); } PruneMissingPlayers(); } } private void ProcessPlayer(PlayerAvatar player, float now, float exitGrace, float standTrigger) { //IL_017f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !((Component)player).gameObject.activeInHierarchy) { return; } int instanceID = ((Object)player).GetInstanceID(); if (!_players.TryGetValue(instanceID, out var value)) { value = new PlayerEntry(player); _players[instanceID] = value; } GameObject gameObject = ((Component)player).gameObject; if (value.Cycle.State != PlayerScaleState.Normal && !_scaling.IsOurs(gameObject)) { value.Cycle.OnExternalRestore(); _scaling.Forget(value.TargetId); ModLog.Debug("Player scale was released outside ShrinkCart: " + ((Object)player).name + " nextIsGrow=" + value.Cycle.NextIsGrow); } GetPlayerCartZone(player, out var inRange, out var inZone); PlayerCycleAction playerCycleAction = value.Cycle.Step(inRange, inZone, now, exitGrace, standTrigger); switch (playerCycleAction) { case PlayerCycleAction.Restore: switch (_scaling.Restore(gameObject, ModConfig.SafeRestoreScaleSpeed())) { case RestoreResult.Restored: value.Cycle.OnRestored(); ModLog.Debug("Restored player after standing in cart center: " + ((Object)player).name + " nextIsGrow=" + value.Cycle.NextIsGrow); break; case RestoreResult.NotOwned: value.Cycle.OnExternalRestore(); break; } break; case PlayerCycleAction.ApplyShrink: case PlayerCycleAction.ApplyGrow: { if (_scaling.IsScaled(gameObject)) { ModLog.Debug("Skipped player cart scale because another scale session is active: " + ((Object)player).name); break; } float factor = ((playerCycleAction == PlayerCycleAction.ApplyGrow) ? ModConfig.SafePlayerCartGrowFactor() : ModConfig.SafePlayerCartScaleFactor()); if (_scaling.TryApply(gameObject, ScaleOptionsFactory.ForPlayer(factor), ScaleKind.Player)) { value.Cycle.OnApplied(playerCycleAction); ModLog.Debug("Applied player cart scale: " + ((Object)player).name + " state=" + value.Cycle.State.ToString() + " factor=" + factor.ToString("0.###")); } else { ModLog.Debug("ScalerCore rejected player cart scale: " + ((Object)player).name + " action=" + playerCycleAction); } break; } } } private void GetPlayerCartZone(PlayerAvatar player, out bool inRange, out bool inZone) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) inRange = false; inZone = false; Vector3 playerStandPoint = GetPlayerStandPoint(player); foreach (CartRegistry.CartInfo item in _carts.All) { if (item.IsSmall || (Object)(object)item.Cart == (Object)null) { continue; } Transform val = item.InCartVolume; if ((Object)(object)val == (Object)null) { val = (item.InCartVolume = GameAccess.InCartVolume(item.Cart)); if ((Object)(object)val == (Object)null) { continue; } } if (IsPointInsideCartFloorProjection(playerStandPoint, val, 1f)) { inRange = true; if (IsPointInsideCartFloorProjection(playerStandPoint, val, 0.45f)) { inZone = true; break; } } } } private static bool IsPointInsideCartFloorProjection(Vector3 point, Transform volume, float horizontalScale) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Vector3 val = Quaternion.Inverse(volume.rotation) * (point - volume.position); Vector3 val2 = volume.localScale * 0.5f; float num = Mathf.Max(Mathf.Abs(val2.x) * horizontalScale, 0.15f); float num2 = Mathf.Max(Mathf.Abs(val2.z) * horizontalScale, 0.15f); float num3 = 0f - Mathf.Abs(val2.y); if (Mathf.Abs(val.x) <= num && Mathf.Abs(val.z) <= num2 && val.y >= num3 - 0.25f) { return val.y <= num3 + 1.4f; } return false; } private static Vector3 GetPlayerStandPoint(PlayerAvatar player) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) Collider val = GameAccess.AvatarCollider(player); if ((Object)(object)val != (Object)null) { Bounds bounds = val.bounds; return new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).min.y + 0.05f, ((Bounds)(ref bounds)).center.z); } return (((Object)(object)player.playerTransform != (Object)null) ? player.playerTransform.position : ((Component)player).transform.position) + Vector3.up * 0.05f; } private static List<PlayerAvatar> GetPlayers() { try { if ((Object)(object)GameDirector.instance != (Object)null && GameDirector.instance.PlayerList != null) { return GameDirector.instance.PlayerList; } return SemiFunc.PlayerGetAll(); } catch (Exception ex) { ModLog.Debug("Player list unavailable this tick: " + ex.Message); return null; } } private void RestoreAll() { foreach (PlayerEntry value in _players.Values) { if (value.Cycle.State != PlayerScaleState.Normal && (Object)(object)value.Player != (Object)null) { _scaling.Restore(((Component)value.Player).gameObject, ModConfig.SafeRestoreScaleSpeed()); } } _players.Clear(); } private void ForgetAll() { foreach (PlayerEntry value in _players.Values) { _scaling.Forget(value.TargetId); } _players.Clear(); } private void PruneMissingPlayers() { _removeIds.Clear(); foreach (KeyValuePair<int, PlayerEntry> player in _players) { if ((Object)(object)player.Value.Player == (Object)null) { _removeIds.Add(player.Key); } } for (int i = 0; i < _removeIds.Count; i++) { _scaling.Forget(_players[_removeIds[i]].TargetId); _players.Remove(_removeIds[i]); } } } } namespace ShrinkCart.Domain { internal static class MathUtil { internal static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } internal static float RoundTo2Decimals(float value) { return (float)Math.Round(value * 100f) / 100f; } internal static bool Approximately(float a, float b) { return Math.Abs(b - a) < Math.Max(1E-06f * Math.Max(Math.Abs(a), Math.Abs(b)), 1.1E-44f); } } internal static class ObjectNames { internal static string Clean(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } return name.Replace("Valuable ", string.Empty).Replace("(Clone)", string.Empty).Trim(); } internal static bool TryParseEnemyOrb(string cleanName, out ShrinkCategory category) { category = ShrinkCategory.Fallback; if (string.IsNullOrEmpty(cleanName) || !cleanName.StartsWith("Enemy", StringComparison.OrdinalIgnoreCase)) { return false; } string[] array = cleanName.Split('-'); if (array.Length < 2) { return false; } string text = array[1].Trim(); if (text.Equals("Small", StringComparison.OrdinalIgnoreCase)) { category = ShrinkCategory.EnemyOrbSmall; return true; } if (text.Equals("Medium", StringComparison.OrdinalIgnoreCase)) { category = ShrinkCategory.EnemyOrbMedium; return true; } if (text.Equals("Big", StringComparison.OrdinalIgnoreCase)) { category = ShrinkCategory.EnemyOrbBig; return true; } if (text.Equals("Berserker", StringComparison.OrdinalIgnoreCase)) { category = ShrinkCategory.EnemyOrbBerserker; return true; } return false; } } internal enum PlayerScaleState { Normal, Shrunk, Grown } internal enum PlayerCycleAction { None, Restore, ApplyShrink, ApplyGrow } internal sealed class PlayerCycle { private bool _wasInRange; private bool _wasInZone; private bool _triggeredThisStay; private float _zoneEnteredAt; private float _lastInRangeAt; internal PlayerScaleState State { get; private set; } internal bool NextIsGrow { get; private set; } internal PlayerCycleAction Step(bool inRange, bool inZone, float now, float exitGraceSeconds, float standTriggerSeconds) { if (inRange) { _lastInRangeAt = now; } else if (_wasInRange && now - _lastInRangeAt <= exitGraceSeconds) { inRange = true; } if (!inRange) { _wasInRange = false; _wasInZone = false; _triggeredThisStay = false; _zoneEnteredAt = 0f; _lastInRangeAt = 0f; return PlayerCycleAction.None; } _wasInRange = true; if (!inZone) { _wasInZone = false; _zoneEnteredAt = 0f; return PlayerCycleAction.None; } if (!_wasInZone) { _wasInZone = true; _zoneEnteredAt = now; _lastInRangeAt = now; return PlayerCycleAction.None; } if (_triggeredThisStay || now - _zoneEnteredAt < standTriggerSeconds) { return PlayerCycleAction.None; } _triggeredThisStay = true; if (State != PlayerScaleState.Normal) { return PlayerCycleAction.Restore; } if (!NextIsGrow) { return PlayerCycleAction.ApplyShrink; } return PlayerCycleAction.ApplyGrow; } internal void OnRestored() { AdvanceToNormal(); } internal void OnExternalRestore() { AdvanceToNormal(); } internal void OnApplied(PlayerCycleAction action) { switch (action) { case PlayerCycleAction.ApplyGrow: State = PlayerScaleState.Grown; break; case PlayerCycleAction.ApplyShrink: State = PlayerScaleState.Shrunk; break; } } private void AdvanceToNormal() { if (State != PlayerScaleState.Normal) { NextIsGrow = State == PlayerScaleState.Shrunk; State = PlayerScaleState.Normal; } } } internal sealed class PresenceTracker { private sealed class Entry { internal int CartId; internal float LastSeen; internal float AbsentSince = float.NaN; internal int SeenInScan; } private readonly Dictionary<int, Entry> _entries = new Dictionary<int, Entry>(); private int _scanCounter; private int _activeScanCartId; private int _activeScanSerial; private float _earliestAbsentSince = float.NaN; internal int Count => _entries.Count; internal bool Contains(int id) { return _entries.ContainsKey(id); } internal bool IsAbsent(int id) { if (_entries.TryGetValue(id, out var value)) { return !float.IsNaN(value.AbsentSince); } return false; } internal void Track(int id, int cartId, float now) { _entries[id] = new Entry { CartId = cartId, LastSeen = now, SeenInScan = CurrentScanSerialFor(cartId) }; } internal bool TryMarkSeen(int id, int cartId, float now) { if (!_entries.TryGetValue(id, out var value)) { return false; } value.CartId = cartId; value.LastSeen = now; value.AbsentSince = float.NaN; value.SeenInScan = CurrentScanSerialFor(cartId); return true; } internal void BeginScan(int cartId) { _scanCounter++; if (_scanCounter == 0) { _scanCounter = 1; } _activeScanCartId = cartId; _activeScanSerial = _scanCounter; } internal void EndScan(int cartId, float now) { int num = ((_activeScanSerial != 0 && _activeScanCartId == cartId) ? _activeScanSerial : (-1)); foreach (Entry value in _entries.Values) { if (value.CartId == cartId && value.SeenInScan != num && float.IsNaN(value.AbsentSince)) { MarkAbsent(value, now); } } _activeScanCartId = 0; _activeScanSerial = 0; } internal void OnCartRemoved(int cartId, float now) { foreach (Entry value in _entries.Values) { if (value.CartId == cartId && float.IsNaN(value.AbsentSince)) { MarkAbsent(value, now); } } } internal bool HasDue(float now, float debounceSeconds) { if (!float.IsNaN(_earliestAbsentSince)) { return now - _earliestAbsentSince >= debounceSeconds; } return false; } internal void CollectDue(float now, float debounceSeconds, List<int> result) { result.Clear(); float num = float.NaN; foreach (KeyValuePair<int, Entry> entry in _entries) { float absentSince = entry.Value.AbsentSince; if (!float.IsNaN(absentSince)) { if (now - absentSince >= debounceSeconds) { result.Add(entry.Key); } if (float.IsNaN(num) || absentSince < num) { num = absentSince; } } } _earliestAbsentSince = num; } internal void CopyIds(List<int> result) { result.Clear(); foreach (int key in _entries.Keys) { result.Add(key); } } internal void Remove(int id) { _entries.Remove(id); } internal void Clear() { _entries.Clear(); _activeScanCartId = 0; _activeScanSerial = 0; _earliestAbsentSince = float.NaN; } private void MarkAbsent(Entry entry, float now) { entry.AbsentSince = now; if (float.IsNaN(_earliestAbsentSince) || now < _earliestAbsentSince) { _earliestAbsentSince = now; } } private int CurrentScanSerialFor(int cartId) { if (_activeScanSerial == 0 || cartId != _activeScanCartId) { return 0; } return _activeScanSerial; } } internal sealed class RetryCooldown { private readonly Dictionary<int, float> _until = new Dictionary<int, float>(); private readonly List<int> _expired = new List<int>(8); internal int Count => _until.Count; internal bool IsCooling(int id, float now) { if (!_until.TryGetValue(id, out var value)) { return false; } if (now < value) { return true; } _until.Remove(id); return false; } internal void Start(int id, float now, float durationSeconds) { _until[id] = now + durationSeconds; } internal void Cancel(int id) { _until.Remove(id); } internal void Prune(float now) { if (_until.Count == 0) { return; } _expired.Clear(); foreach (KeyValuePair<int, float> item in _until) { if (now >= item.Value) { _expired.Add(item.Key); } } for (int i = 0; i < _expired.Count; i++) { _until.Remove(_expired[i]); } } internal void Clear() { _until.Clear(); } } internal static class ScanCadence { internal const int FewTrackedThreshold = 6; internal const int SomeTrackedThreshold = 15; internal const int ManyTrackedThreshold = 30; internal const float SomeTrackedIntervalSeconds = 0.35f; internal const float ManyTrackedIntervalSeconds = 0.75f; internal static float Interval(bool dynamicEnabled, int trackedCount, float minimumSeconds, float maximumSeconds) { if (maximumSeconds < minimumSeconds) { maximumSeconds = minimumSeconds; } if (!dynamicEnabled) { return minimumSeconds; } float value = ((trackedCount <= 0) ? maximumSeconds : ((trackedCount <= 6) ? minimumSeconds : ((trackedCount <= 15) ? 0.35f : ((trackedCount > 30) ? maximumSeconds : 0.75f)))); return MathUtil.Clamp(value, minimumSeconds, maximumSeconds); } } internal enum ShrinkCategory { Tiny, Small, Medium, Big, Wide, Tall, VeryTall, EnemyOrbSmall, EnemyOrbMedium, EnemyOrbBig, EnemyOrbBerserker, Surplus, ValuableBox, Fallback } } namespace ShrinkCart.Core { internal sealed class Authority { private const float CheckIntervalSeconds = 0.25f; private float _nextCheckTime; internal bool IsHost { get; private set; } internal bool Poll(float unscaledNow) { if (unscaledNow < _nextCheckTime) { return IsHost; } _nextCheckTime = unscaledNow + 0.25f; try { IsHost = SemiFunc.IsMasterClientOrSingleplayer(); } catch (Exception ex) { IsHost = false; ModLog.WarnOnce("authority", "Host check failed; ShrinkCart stays passive until it succeeds: " + ex.Message); } return IsHost; } } internal static class DebugCounters { private const float ReportIntervalSeconds = 10f; internal static int TriggerCalls; internal static int TriggerFastPath; internal static int ScansProcessed; internal static int Classifications; private static float _nextReportTime; internal static void MaybeReport(float now) { if (!(now < _nextReportTime)) { _nextReportTime = now + 10f; if (ModLog.DebugEnabled && (TriggerCalls != 0 || ScansProcessed != 0 || Classifications != 0)) { ModLog.Debug("Counters (last " + 10f + "s): triggerCalls=" + TriggerCalls + " triggerFastPath=" + TriggerFastPath + " scansProcessed=" + ScansProcessed + " classified=" + Classifications); } Reset(); } } internal static void Reset() { TriggerCalls = 0; TriggerFastPath = 0; ScansProcessed = 0; Classifications = 0; } } internal sealed class FeatureAvailability { private readonly List<string>[] _problems = new List<string>[8]; internal void MarkUnavailable(FeatureId feature, string reason) { List<string> list = _problems[(int)feature]; if (list == null) { list = new List<string>(); _problems[(int)feature] = list; } if (!list.Contains(reason)) { list.Add(reason); } } internal bool IsAvailable(FeatureId feature) { if (HasProblems(feature)) { return false; } FeatureId[] array = FeatureIds.DependenciesOf(feature); for (int i = 0; i < array.Length; i++) { if (HasProblems(array[i])) { return false; } } return true; } internal string Describe() { StringBuilder stringBuilder = null; for (int i = 0; i < 8; i++) { FeatureId featureId = (FeatureId)i; if (!IsAvailable(featureId)) { stringBuilder = ((stringBuilder == null) ? new StringBuilder("Unavailable features: ") : stringBuilder.Append("; ")); stringBuilder.Append(featureId); List<string> list = _problems[i]; stringBuilder.Append((list != null && list.Count > 0) ? (" (" + string.Join(", ", list.ToArray()) + ")") : " (dependency unavailable)"); } } if (stringBuilder != null) { return stringBuilder.ToString(); } return "All features available."; } private bool HasProblems(FeatureId feature) { List<string> list = _problems[(int)feature]; if (list != null) { return list.Count > 0; } return false; } } internal enum FeatureId { Core, CartObservation, ItemShrink, CartNestingGuard, EnemyCrush, DeadHeadShrink, PlayerScaling, ValuableBoxSupport } internal static class FeatureIds { internal const int Count = 8; private static readonly FeatureId[] None = new FeatureId[0]; private static readonly FeatureId[] CoreOnly = new FeatureId[1]; private static readonly FeatureId[] CoreAndObservation = new FeatureId[2] { FeatureId.Core, FeatureId.CartObservation }; internal static FeatureId[] DependenciesOf(FeatureId feature) { switch (feature) { case FeatureId.Core: case FeatureId.ValuableBoxSupport: return None; case FeatureId.CartObservation: return CoreOnly; default: return CoreAndObservation; } } } internal enum DeactivateReason { Disabled, AuthorityLost, Unavailable, Shutdown } internal interface IFeature { FeatureId Id { get; } bool IsConfiguredEnabled { get; } void Activate(float now); void Deactivate(DeactivateReason reason, float now); void ResetForLevel(float now); void Tick(float now); } internal sealed class ModLifecycle { private readonly Authority _authority; private readonly FeatureAvailability _availability; private readonly Func<bool> _modEnabled; private readonly Action _onAuthorityGained; private readonly Action<float> _onLevelReset; private readonly List<IFeature> _features = new List<IFeature>(); private readonly bool[] _active = new bool[8]; private readonly HashSet<string> _reportedFailures = new HashSet<string>(); private bool _wasHost; internal bool IsHost => _wasHost; internal ModLifecycle(Authority authority, FeatureAvailability availability, Func<bool> modEnabled, Action onAuthorityGained, Action<float> onLevelReset) { _authority = authority; _availability = availability; _modEnabled = modEnabled; _onAuthorityGained = onAuthorityGained; _onLevelReset = onLevelReset; } internal bool IsActive(FeatureId feature) { return _active[(int)feature]; } internal bool IsAvailable(FeatureId feature) { return _availability.IsAvailable(feature); } internal void Register(IFeature feature) { _features.Add(feature); } internal void Update(float now, float unscaledNow) { bool flag = _authority.Poll(unscaledNow); if (flag != _wasHost) { _wasHost = flag; if (flag) { try { _onAuthorityGained(); } catch (Exception ex) { ReportFailure(FeatureId.CartObservation, "authority gained", ex); } } } bool flag2 = SafeModEnabled(); for (int i = 0; i < _features.Count; i++) { IFeature feature = _features[i]; int id = (int)feature.Id; bool flag3 = _availability.IsAvailable(feature.Id); bool flag4 = flag && flag2 && flag3 && SafeConfiguredEnabled(feature); if (flag4 && !_active[id]) { Activate(feature, now); } else if (!flag4 && _active[id]) { DeactivateReason reason = ((!flag) ? DeactivateReason.AuthorityLost : ((!flag3) ? DeactivateReason.Unavailable : DeactivateReason.Disabled)); Deactivate(feature, reason, now); } if (_active[id]) { try { feature.Tick(now); } catch (Exception ex2) { Fail(feature, "tick", ex2, now); } } } } internal void OnLevelChanging(float now) { for (int i = 0; i < _features.Count; i++) { IFeature feature = _features[i]; if (_active[(int)feature.Id]) { try { feature.ResetForLevel(now); } catch (Exception ex) { Fail(feature, "level reset", ex, now); } } } try { _onLevelReset(now); } catch (Exception ex2) { ReportFailure(FeatureId.CartObservation, "level reset", ex2); } } internal void Shutdown(float now) { for (int i = 0; i < _features.Count; i++) { IFeature feature = _features[i]; if (_active[(int)feature.Id]) { Deactivate(feature, DeactivateReason.Shutdown, now); } } } internal void ReportFailure(FeatureId feature, string context, Exception ex) { string text = "runtime error in " + context + ": " + ex.GetBaseException().Message; _availability.MarkUnavailable(feature, text); if (_reportedFailures.Add(feature.ToString() + "|" + context)) { ModLog.Error("ShrinkCart feature " + feature.ToString() + " disabled after " + text + Environment.NewLine + ex); } } private void Activate(IFeature feature, float now) { try { feature.Activate(now); _active[(int)feature.Id] = true; ModLog.Debug("Feature activated: " + feature.Id); } catch (Exception ex) { _active[(int)feature.Id] = false; ReportFailure(feature.Id, "activate", ex); } } private void Deactivate(IFeature feature, DeactivateReason reason, float now) { _active[(int)feature.Id] = false; try { feature.Deactivate(reason, now); ModLog.Debug("Feature deactivated: " + feature.Id.ToString() + " reason=" + reason); } catch (Exception ex) { ReportFailure(feature.Id, "deactivate", ex); } } private void Fail(IFeature feature, string context, Exception ex, float now) { ReportFailure(feature.Id, context, ex); if (_active[(int)feature.Id]) { Deactivate(feature, DeactivateReason.Unavailable, now); } } private bool SafeModEnabled() { try { return _modEnabled(); } catch { return false; } } private static bool SafeConfiguredEnabled(IFeature feature) { try { return feature.IsConfiguredEnabled; } catch { return false; } } } internal static class ModLog { private static readonly HashSet<string> OnceKeys = new HashSet<string>(); private static ManualLogSource _source; private static Func<bool> _debugEnabled; internal static bool DebugEnabled { get { try { return _debugEnabled != null && _debugEnabled(); } catch { return false; } } } internal static void Initialize(ManualLogSource source, Func<bool> debugEnabled) { _source = source; _debugEnabled = debugEnabled; OnceKeys.Clear(); } internal static void Info(string message) { if (_source != null) { _source.LogInfo((object)message); } } internal static void Warn(string message) { if (_source != null) { _source.LogWarning((object)message); } } internal static void Error(string message) { if (_source != null) { _source.LogError((object)message); } } internal static void Debug(string message) { if (_source != null && DebugEnabled) { _source.LogInfo((object)("[debug] " + message)); } } internal static void WarnOnce(string key, string message) { if (OnceKeys.Add(key)) { Warn(message); } } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class PatchFeatureAttribute : Attribute { internal FeatureId Feature { get; } public string Frequency { get; set; } public string Side { get; set; } internal PatchFeatureAttribute(FeatureId feature) { Feature = feature; } } } namespace ShrinkCart.Config { internal sealed class ConfigKeyDefinition { internal string Id { get; } internal FeatureId Feature { get; } internal string ChineseSection { get; } internal string EnglishSection { get; } internal string ChineseKey { get; } internal string EnglishKey { get; } internal Type ValueType { get; } internal object DefaultValue { get; } internal float? Minimum { get; } internal float? Maximum { get; } internal string ChineseDescription { get; } internal string EnglishDescription { get; } internal string Since { get; } internal ConfigKeyDefinition(string id, FeatureId feature, string chineseSection, string englishSection, string chineseKey, string englishKey, Type valueType, object defaultValue, float? minimum, float? maximum, string chineseDescription, string englishDescription, string since) { Id = id; Feature = feature; ChineseSection = chineseSection; EnglishSection = englishSection; ChineseKey = chineseKey; EnglishKey = englishKey; ValueType = valueType; DefaultValue = defaultValue; Minimum = minimum; Maximum = maximum; ChineseDescription = chineseDescription; EnglishDescription = englishDescription; Since = since; } internal string Section(bool english) { if (!english) { return ChineseSection; } return EnglishSection; } internal string Key(bool english) { if (!english) { return ChineseKey; } return EnglishKey; } internal ConfigDescription CreateDescription(bool english) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown string text = (english ? EnglishDescription : ChineseDescription); if (ValueType == typeof(float) && Minimum.HasValue && Maximum.HasValue) { return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(Minimum.Value, Maximum.Value), Array.Empty<object>()); } return new ConfigDescription(text, (AcceptableValueBase)null, Array.Empty<object>()); } } internal static class ConfigMigrator { internal sealed class LegacyDefault { internal ConfigKeyDefinition Target { get; } internal float[] OldDefaults { get; } internal LegacyDefault(ConfigKeyDefinition target, params float[] oldDefaults) { Target = target; OldDefaults = oldDefaults; } internal bool IsOldDefault(float stored) { for (int i = 0; i < OldDefaults.Length; i++) { if (MathUtil.RoundTo2Decimals(OldDefaults[i]) == stored) { return true; } } return false; } } internal sealed class LegacyRename { internal ConfigKeyDefinition Target { get; } internal string[] SectionKeyPairs { get; } internal LegacyRename(ConfigKeyDefinition target, params string[] sectionKeyPairs) { Target = target; SectionKeyPairs = sectionKeyPairs; } } internal const int CurrentSchemaVersion = 1; internal const string BackupSuffix = ".pre-0.3.1.bak"; internal const string LegacyMassMarkerName = "ShrinkCart.v0.2.39.mass-default-enabled"; internal static readonly LegacyRename[] LegacyRenames = new LegacyRename[7] { new LegacyRename(ConfigSchema.ReshrinkCooldownSeconds, "购物车", "取出后恢复延迟", "Cart", "RestoreGraceSeconds"), new LegacyRename(ConfigSchema.SurplusEnabled, "特殊物品", "启用 Surplus 缩小"), new LegacyRename(ConfigSchema.SurplusScaleFactor, "特殊物品", "Surplus 倍率"), new LegacyRename(ConfigSchema.FallbackScaleFactor, "普通或未知物品", "默认缩小倍率"), new LegacyRename(ConfigSchema.EnemyInCartInstantKill, "车辆碾压", "车辆碾压秒杀敌人", "VehicleCrush", "InstantKillEnemies"), new LegacyRename(ConfigSchema.TinyEnabled, "Tiny 小型贵重物", "启用此分类缩小"), new LegacyRename(ConfigSchema.TinyScaleFactor, "Tiny 小型贵重物", "缩小倍率") }; internal static readonly LegacyDefault[] LegacyDefaults = new LegacyDefault[14] { new LegacyDefault(ConfigSchema.CartLeaveDebounceSeconds, 2.5f), new LegacyDefault(ConfigSchema.ReshrinkCooldownSeconds, 0.2f), new LegacyDefault(ConfigSchema.TinyScaleFactor, 0.55f), new LegacyDefault(ConfigSchema.SmallScaleFactor, 0.45f), new LegacyDefault(ConfigSchema.MediumScaleFactor, 0.35f, 0.33f, 0.4f), new LegacyDefault(ConfigSchema.BigScaleFactor, 0.3f, 0.2f, 0.25f), new LegacyDefault(ConfigSchema.WideScaleFactor, 0.3f, 0.2f, 0.25f), new LegacyDefault(ConfigSchema.TallScaleFactor, 0.28f, 0.3f, 0.25f), new LegacyDefault(ConfigSchema.VeryTallScaleFactor, 0.2f), new LegacyDefault(ConfigSchema.EnemyOrbSmallScaleFactor, 0.35f, 0.6f), new LegacyDefault(ConfigSchema.EnemyOrbMediumScaleFactor, 0.3f, 0.55f), new LegacyDefault(ConfigSchema.EnemyOrbBigScaleFactor, 0.25f, 0.2f), new LegacyDefault(ConfigSchema.EnemyOrbBerserkerScaleFactor, 0.25f, 0.2f), new LegacyDefault(ConfigSchema.FallbackScaleFactor, 0.4f) }; internal static readonly string[] DeprecatedKeys = new string[78] { "ShrinkCart.Migration", "SpeedScaleV2Migrated", "Cart", "Enabled", "Cart", "ScaleFactor", "Cart", "ScaleSpeed", "Cart", "PreserveMass", "Cart", "ShrinkNonValuableItems", "Cart", "SuppressValuableDamageRestore", "Diagnostics", "DebugLogging", "购物车", "保持原始重量", "VehicleCrush", "InstantKillPlayers", "视觉", "隐藏缩放闪光", "车辆碾压", "车辆碾压秒杀玩家", "提取点复活兼容", "启用提取点复活", "提取点复活兼容", "复活前稳定检测时间", "提取点复活兼容", "复活检测间隔", "提取点复活兼容", "拦截外部立即复活调用", "购物车", "商店用品也缩小", "购物车", "玩家也缩小", "购物车", "启用实验性玩家缩放", "购物车", "玩家死亡前自动恢复", "购物车", "启用玩家缩放", "购物车", "玩家进车缩放倍率", "购物车", "玩家站车触发时间", "购物车", "玩家离车判定宽容时间", "购物车", "玩家进车切换间隔", "购物车", "普通物品也缩小", "购物车", "商店/人物用品也缩小", "购物车", "防止车辆互相重叠", "购物车", "车辆硬碰撞修正强度", "购物车", "车辆最大单帧修正距离", "购物车", "车辆挤压速度清除", "购物车", "车辆临时忽略碰撞时间(已废弃)", "购物车", "车辆临时忽略碰撞时间", "购物车", "车辆脱困强度", "玩家缩放", "旧版玩家缩放开关(已停用)", "地图兼容", "Minecraft Stronghold 普通门进车破碎", "地图兼容", "Minecraft Stronghold 门破碎确认时间", "Map Compatibility", "Minecraft Stronghold door shatter in cart", "Map Compatibility", "Minecraft Stronghold door shatter confirmation time" }; internal static int TakeStoredSchemaVersion(RawConfigAccess raw) { if (raw.TryTake("ShrinkCart.Meta", "ConfigSchemaVersion", out var raw2) && int.TryParse(raw2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } internal static void BackupConfigFile(string configPath) { try { string text = configPath + ".pre-0.3.1.bak"; if (!File.Exists(text)) { File.Copy(configPath, text); } } catch (Exception ex) { ModLog.Warn("Failed to back up config before migration: " + ex.Message); } } internal static void ApplyRenamesAndCleanup(RawConfigAccess raw, LocalizedConfigBinder binder) { for (int i = 0; i < LegacyRenames.Length; i++) { LegacyRename legacyRename = LegacyRenames[i]; string text = null; for (int j = 0; j + 1 < legacyRename.SectionKeyPairs.Length; j += 2) { if (raw.TryTake(legacyRename.SectionKeyPairs[j], legacyRename.SectionKeyPairs[j + 1], out var raw2) && text == null) { text = raw2; } } if (text != null && !binder.HadStoredValue(legacyRename.Target)) { binder.Entry(legacyRename.Target).SetSerializedValue(text); ModLog.Info("Inherited legacy config value for " + legacyRename.Target.Id + "."); } } for (int k = 0; k + 1 < DeprecatedKeys.Length; k += 2) { raw.Delete(DeprecatedKeys[k], DeprecatedKeys[k + 1]); } } internal static void MigrateToCurrent(int storedVersion, string configDirectory, LocalizedConfigBinder binder) { if (storedVersion < 1) { ModConfig.CartScaleSpeed.Value = (float)ConfigSchema.CartScaleSpeed.DefaultValue; ModConfig.RestoreScaleSpeed.Value = (float)ConfigSchema.RestoreScaleSpeed.DefaultValue; string text = UpgradeLegacyDefaults(binder); DeleteLegacyMassMarker(configDirectory); ModLog.Info("Config migrated to schema " + 1 + ": shrink/restore speed reset once to defaults " + ModConfig.CartScaleSpeed.Value.ToString("0.##", CultureInfo.InvariantCulture) + " / " + ModConfig.RestoreScaleSpeed.Value.ToString("0.##", CultureInfo.InvariantCulture) + " (actual speed = value x 0.1)" + ((text.Length > 0) ? ("; settings still at old defaults upgraded: " + text) : "; no settings at old defaults") + ". Previous file kept as *.pre-0.3.1.bak."); } } private static string UpgradeLegacyDefaults(LocalizedConfigBinder binder) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < LegacyDefaults.Length; i++) { LegacyDefault legacyDefault = LegacyDefaults[i]; if (!(binder.Entry(legacyDefault.Target) is ConfigEntry<float> val)) { continue; } float stored = MathUtil.RoundTo2Decimals(val.Value); if (legacyDefault.IsOldDefault(stored)) { float num = (val.Value = (float)legacyDefault.Target.DefaultValue); if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(legacyDefault.Target.Id).Append(' ').Append(stored.ToString("0.##", CultureInfo.InvariantCulture)) .Append("->") .Append(num.ToString("0.##", CultureInfo.InvariantCulture)); } } return stringBuilder.ToString(); } private static void DeleteLegacyMassMarker(string configDirectory) { if (string.IsNullOrEmpty(configDirectory)) { return; } try { string path = Path.Combine(configDirectory, "ShrinkCart.v0.2.39.mass-default-enabled"); if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { ModLog.Warn("Failed to delete legacy mass migration marker: " + ex.Message); } } } internal static class ConfigSchema { internal const string ChineseLanguage = "中文"; internal const string EnglishLanguage = "English"; internal const string LanguageSection = "语言 / Language"; internal const string LanguageKey = "配置语言 / Config Language"; internal const string MetaSection = "ShrinkCart.Meta"; internal const string SchemaVersionKey = "ConfigSchemaVersion"; private const string Before031 = "≤0.2.52"; private static readonly List<ConfigKeyDefinition> Ordered = new List<ConfigKeyDefinition>(); internal static readonly ConfigKeyDefinition ModEnabled = Bool("ModEnabled", FeatureId.Core, "通用", "General", "启用 ShrinkCart", "Enable ShrinkCart", defaultValue: true, "总开关。关闭后 ShrinkCart 的全部主机侧功能(物品缩小、玩家缩放、死亡头颅、敌人进车秒杀、车类物品拦截)都会停止,并恢复已被缩放的对象。", "Master switch. When disabled, every host-side ShrinkCart feature (item shrinking, player scaling, death heads, enemy instant kill, cart nesting guard) stops and scaled objects are restored.", "0.3.1"); internal static readonly ConfigKeyDefinition CartShrinkingEnabled = Bool("CartShrinkingEnabled", FeatureId.ItemShrink, "购物车", "Cart", "启用购物车缩小", "Enable cart shrinking", defaultValue: true, "启用后,放进 C.A.R.T / 购物车的支持物品会自动缩小,取出后恢