using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BattleScars.Configuration; using BattleScars.Services; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("Vippy")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BattleScars")] [assembly: AssemblyTitle("BattleScars")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BattleScars { [BepInPlugin("Vippy.BattleScars", "BattleScars", "1.0.0")] public class BattleScars : BaseUnityPlugin { private Harmony? _harmony; internal static BattleScars Instance { get; private set; } internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; private void Awake() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; PluginConfig.Init(((BaseUnityPlugin)this).Config); BindModeRevert(); _harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); _harmony.PatchAll(); Log.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded."); } private static void BindModeRevert() { PluginConfig.Mode.SettingChanged += delegate { PlayerAvatar val = PlayerLookup.LocalAvatar(); if (!((Object)(object)val == (Object)null)) { if (PluginConfig.Mode.Value == RunMode.Off) { Cosmetics.RestoreToLocal(val); Effects.CancelVoice(val); Driver.Instance?.InvalidateAppliedCosmetics(); } else if (PluginConfig.Mode.Value == RunMode.VisualOnly) { Effects.CancelVoice(val); } } }; } } internal static class BuildInfo { public const string Version = "1.0.0"; } } namespace BattleScars.Services { public static class ConfigService { public static bool IsEnabled() { return PluginConfig.Mode.Value != RunMode.Off; } public static bool IsVisualOnly() { return PluginConfig.Mode.Value == RunMode.VisualOnly; } public static bool CosmeticsEnabled() { return IsEnabled() && PluginConfig.EnableCosmetics.Value; } public static bool SparkParticlesEnabled() { return IsEnabled() && PluginConfig.EnableSparkParticles.Value; } public static bool ScreenOverlayEnabled() { return IsEnabled() && PluginConfig.EnableScreenOverlay.Value; } public static bool SpeedNerfEnabled() { return IsEnabled() && !IsVisualOnly() && PluginConfig.EnableSpeedNerf.Value; } public static bool StaminaNerfEnabled() { return IsEnabled() && !IsVisualOnly() && PluginConfig.EnableStaminaNerf.Value; } public static bool VoiceEffectsEnabled() { return IsEnabled() && !IsVisualOnly() && PluginConfig.EnableVoiceEffects.Value; } public static int TestHealthOverride() { return PluginConfig.TestHealth.Value; } public static int CosmeticCountForHealth(int currentHP) { if (currentHP > 75) { return 0; } return (75 - currentHP) / 8 + 1; } public static CosmeticPool PoolForHealth(int currentHP) { if (currentHP <= 50) { return CosmeticPool.Damaged; } if (currentHP <= 60) { return CosmeticPool.Bandages; } return CosmeticPool.Rusty; } public static bool WreckedFaceActive(int currentHP) { return currentHP <= 25 && !string.IsNullOrWhiteSpace("Broken"); } public static Tier TierForHealth(int currentHP) { int num = CosmeticCountForHealth(currentHP); if (num <= 0) { return Tier.Healthy; } if (num <= 2) { return Tier.Scratched; } if (num <= 4) { return Tier.Damaged; } if (num == 5) { return Tier.Battered; } return Tier.Wrecked; } public static float SpeedMultiplierFor(Tier tier) { return Mathf.Lerp(1f, 0.7f, (float)tier / 4f); } public static float StaminaMultiplierFor(Tier tier) { return Mathf.Lerp(1f, 0.45f, (float)tier / 4f); } } public enum CosmeticPool { Rusty, Bandages, Damaged } public static class Cosmetics { private static List? _rustyPool; private static List? _bandagesPool; private static List? _damagedPool; private static List? _wreckedFacePool; private static bool _discoveryRan; public static void DiscoverIfNeeded() { if (!_discoveryRan && !((Object)(object)MetaManager.instance == (Object)null) && MetaManager.instance.cosmeticAssets != null) { List cosmeticAssets = MetaManager.instance.cosmeticAssets; _rustyPool = BuildPool(cosmeticAssets, "Rusty"); _bandagesPool = BuildPool(cosmeticAssets, "Bandages"); _damagedPool = BuildPool(cosmeticAssets, "Damaged,Cracks"); _wreckedFacePool = BuildPool(cosmeticAssets, "Broken"); _discoveryRan = true; int num = _rustyPool.Count + _bandagesPool.Count + _damagedPool.Count + _wreckedFacePool.Count; BattleScars.Log.LogInfo((object)$"[Cosmetics] pools rusty={_rustyPool.Count} bandages={_bandagesPool.Count} damaged={_damagedPool.Count} wreckedFace={_wreckedFacePool.Count}"); if (num == 0) { BattleScars.Log.LogWarning((object)"[Cosmetics] no pool matches, cosmetic effects disabled"); } } } private static List BuildPool(IList assets, string allowList) { List list = ParseList(allowList); List list2 = new List(); if (list.Count == 0) { return list2; } for (int i = 0; i < assets.Count; i++) { CosmeticAsset val = assets[i]; if (!((Object)(object)val == (Object)null)) { string a = (((Object)val).name ?? string.Empty).ToLowerInvariant(); string b = (val.assetName ?? string.Empty).ToLowerInvariant(); if (list.Any((string t) => a.Contains(t) || b.Contains(t))) { list2.Add(i); } } } return list2; } private static List ParseList(string raw) { List list = new List(); if (string.IsNullOrWhiteSpace(raw)) { return list; } string[] array = raw.Split(','); foreach (string text in array) { string text2 = text.Trim().ToLowerInvariant(); if (text2.Length > 0) { list.Add(text2); } } return list; } public static List PickForCount(string steamID, int count, CosmeticPool pool) { //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if (count <= 0) { return new List(); } if (1 == 0) { } List list = pool switch { CosmeticPool.Rusty => _rustyPool, CosmeticPool.Bandages => _bandagesPool, CosmeticPool.Damaged => _damagedPool, _ => _bandagesPool, }; if (1 == 0) { } List list2 = list; if (list2 == null || list2.Count == 0) { return new List(); } List list3 = MetaManager.instance?.cosmeticAssets; if (list3 == null) { return new List(); } int seed = (string.IsNullOrEmpty(steamID) ? 1 : steamID.GetHashCode()); Random rng = new Random(seed); List list4 = list2.OrderBy((int _) => rng.Next()).ToList(); List list5 = new List(count); HashSet hashSet = new HashSet(); foreach (int item in list4) { if (list5.Count >= count) { break; } if (item >= 0 && item < list3.Count) { CosmeticAsset val = list3[item]; if (!((Object)(object)val == (Object)null) && !hashSet.Contains(val.type)) { list5.Add(item); hashSet.Add(val.type); } } } if (list5.Count < count) { foreach (int item2 in list4) { if (list5.Count >= count) { break; } if (!list5.Contains(item2)) { list5.Add(item2); } } } return list5; } public static IReadOnlyList WreckedFaceIndices() { IReadOnlyList wreckedFacePool = _wreckedFacePool; return wreckedFacePool ?? Array.Empty(); } public static List Merge(IList? assets, IList ownList, IList forced) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) List list = new List(ownList.Count + forced.Count); HashSet hashSet = new HashSet(); if (assets != null) { foreach (int item in forced) { if (item >= 0 && item < assets.Count) { CosmeticAsset val = assets[item]; if ((Object)(object)val != (Object)null) { hashSet.Add(val.type); } } } } foreach (int item2 in forced) { if (!list.Contains(item2)) { list.Add(item2); } } foreach (int own in ownList) { if (list.Contains(own)) { continue; } if (assets != null && own >= 0 && own < assets.Count) { CosmeticAsset val2 = assets[own]; if ((Object)(object)val2 != (Object)null && hashSet.Contains(val2.type)) { continue; } } list.Add(own); } return list; } public static void ApplyForState(PlayerAvatar avatar, int count, CosmeticPool pool, bool wreckedFace) { if ((Object)(object)avatar == (Object)null || (Object)(object)avatar.playerCosmetics == (Object)null || (!avatar.photonView.IsMine && SemiFunc.IsMultiplayer())) { return; } List assets = MetaManager.instance?.cosmeticAssets; List ownList = avatar.playerCosmetics.cosmeticEquippedRaw ?? new List(); List list = PickForCount(avatar.steamID, count, pool); IReadOnlyList readOnlyList2; if (!wreckedFace) { IReadOnlyList readOnlyList = Array.Empty(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = WreckedFaceIndices(); } IReadOnlyList readOnlyList3 = readOnlyList2; List list2 = new List(list.Count + readOnlyList3.Count); foreach (int item in readOnlyList3) { list2.Add(item); } foreach (int item2 in list) { if (!list2.Contains(item2)) { list2.Add(item2); } } List list3 = Merge(assets, ownList, list2); using (CosmeticReassertGuard.Enter()) { avatar.playerCosmetics.SetupCosmetics(SemiFunc.IsMultiplayer(), true, list3); avatar.playerCosmetics.SetupColors(SemiFunc.IsMultiplayer(), (int[])null); } } public static void RestoreToLocal(PlayerAvatar avatar) { if ((Object)(object)avatar == (Object)null || (Object)(object)avatar.playerCosmetics == (Object)null || (!avatar.photonView.IsMine && SemiFunc.IsMultiplayer())) { return; } using (CosmeticReassertGuard.Enter()) { avatar.playerCosmetics.SetupCosmetics(SemiFunc.IsMultiplayer(), true, (List)null); avatar.playerCosmetics.SetupColors(SemiFunc.IsMultiplayer(), (int[])null); } } } internal static class CosmeticReassertGuard { [StructLayout(LayoutKind.Sequential, Size = 1)] public struct Releaser : IDisposable { public void Dispose() { if (_depth > 0) { _depth--; } } } [ThreadStatic] private static int _depth; public static bool IsInside => _depth > 0; public static Releaser Enter() { _depth++; return default(Releaser); } } public class Driver : MonoBehaviour { private struct AppliedCosmeticState { public int Count; public CosmeticPool Pool; public bool Face; } private const float SlowTickInterval = 1f; private const float VoiceTickInterval = 0.5f; private float _slowTickTimer; private float _voiceTickTimer; private readonly Dictionary _applied = new Dictionary(); public static Driver? Instance { get; private set; } private void Awake() { Instance = this; Cosmetics.DiscoverIfNeeded(); } private void Update() { HandleDevHotkeys(); if ((Object)(object)StatsManager.instance == (Object)null) { return; } PlayerAvatar val = PlayerLookup.LocalAvatar(); bool flag = ConfigService.IsEnabled(); bool flag2 = (Object)(object)val != (Object)null && (val.deadSet || val.isDisabled); Tier tier = Tier.Healthy; if (flag && !flag2 && (Object)(object)val != (Object)null) { tier = ConfigService.TierForHealth(EffectiveHealthFor(val)); } if ((Object)(object)val != (Object)null) { Effects.ApplySpeedTick(val, tier); Effects.ApplyStaminaTick(val, tier); } _voiceTickTimer -= Time.deltaTime; if (_voiceTickTimer <= 0f && (Object)(object)val != (Object)null) { _voiceTickTimer = 0.5f; if (tier == Tier.Healthy) { Effects.CancelVoice(val); } else { Effects.ApplyVoiceTick(val, tier); } } _slowTickTimer -= Time.deltaTime; if (!(_slowTickTimer > 0f)) { _slowTickTimer = 1f; SlowTick(val, flag, flag2); } } private void HandleDevHotkeys() { int? num = null; if (Input.GetKeyDown((KeyCode)256)) { num = -1; } else if (Input.GetKeyDown((KeyCode)257)) { num = 1; } else if (Input.GetKeyDown((KeyCode)258)) { num = 20; } else if (Input.GetKeyDown((KeyCode)259)) { num = 30; } else if (Input.GetKeyDown((KeyCode)260)) { num = 40; } else if (Input.GetKeyDown((KeyCode)261)) { num = 50; } else if (Input.GetKeyDown((KeyCode)262)) { num = 60; } else if (Input.GetKeyDown((KeyCode)263)) { num = 70; } else if (Input.GetKeyDown((KeyCode)264)) { num = 80; } else if (Input.GetKeyDown((KeyCode)265)) { num = 90; } if (num.HasValue && PluginConfig.TestHealth.Value != num.Value) { PluginConfig.TestHealth.Value = num.Value; BattleScars.Log.LogInfo((object)("[Dev] TestHealth -> " + ((num.Value < 0) ? "off" : num.Value.ToString()))); InvalidateAppliedCosmetics(); } } public static int EffectiveHealthFor(PlayerAvatar avatar) { int value = PluginConfig.TestHealth.Value; if (value >= 0) { return value; } return ((Object)(object)avatar.playerHealth != (Object)null) ? avatar.playerHealth.health : 0; } private void SlowTick(PlayerAvatar? local, bool enabled, bool deadOrDisabled) { SaveBackup.TryBackupOnce(local); if ((Object)(object)local == (Object)null || string.IsNullOrEmpty(local.steamID)) { return; } int currentHP = EffectiveHealthFor(local); bool flag = enabled && !deadOrDisabled; int num = (flag ? ConfigService.CosmeticCountForHealth(currentHP) : 0); CosmeticPool cosmeticPool = (flag ? ConfigService.PoolForHealth(currentHP) : CosmeticPool.Rusty); bool flag2 = flag && ConfigService.WreckedFaceActive(currentHP); _applied.TryGetValue(local.steamID, out var value); if (value.Count == num && value.Pool == cosmeticPool && value.Face == flag2) { return; } _applied[local.steamID] = new AppliedCosmeticState { Count = num, Pool = cosmeticPool, Face = flag2 }; if (ConfigService.CosmeticsEnabled()) { if (num <= 0) { Cosmetics.RestoreToLocal(local); } else { Cosmetics.ApplyForState(local, num, cosmeticPool, flag2); } } } public void InvalidateAppliedCosmetics() { _applied.Clear(); } public void ReassertLocalCosmeticsImmediate() { PlayerAvatar val = PlayerLookup.LocalAvatar(); if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.steamID) && ConfigService.IsEnabled() && ConfigService.CosmeticsEnabled() && !val.deadSet && !val.isDisabled) { int currentHP = EffectiveHealthFor(val); int num = ConfigService.CosmeticCountForHealth(currentHP); if (num > 0) { CosmeticPool pool = ConfigService.PoolForHealth(currentHP); bool flag = ConfigService.WreckedFaceActive(currentHP); _applied[val.steamID] = new AppliedCosmeticState { Count = num, Pool = pool, Face = flag }; Cosmetics.ApplyForState(val, num, pool, flag); } } } } public static class Effects { public static void ApplySpeedTick(PlayerAvatar avatar, Tier tier) { if (ConfigService.SpeedNerfEnabled() && tier != 0 && !((Object)(object)avatar == (Object)null) && avatar.isLocal) { PlayerController instance = PlayerController.instance; if (!((Object)(object)instance == (Object)null)) { instance.OverrideSpeed(ConfigService.SpeedMultiplierFor(tier), 0.2f); } } } public static void ApplyStaminaTick(PlayerAvatar avatar, Tier tier) { if (!ConfigService.StaminaNerfEnabled() || tier == Tier.Healthy || (Object)(object)avatar == (Object)null || !avatar.isLocal) { return; } PlayerController instance = PlayerController.instance; if (!((Object)(object)instance == (Object)null)) { float num = instance.EnergyStart * ConfigService.StaminaMultiplierFor(tier); if (instance.EnergyCurrent > num) { instance.EnergyCurrent = num; } } } public static void ApplyVoiceTick(PlayerAvatar avatar, Tier tier) { if (ConfigService.VoiceEffectsEnabled() && tier != 0 && !((Object)(object)avatar == (Object)null) && avatar.isLocal && !((Object)(object)avatar.voiceChat == (Object)null) && avatar.voiceChatFetched) { if (1 == 0) { } float num = tier switch { Tier.Scratched => 0.95f, Tier.Damaged => 0.88f, Tier.Battered => 0.78f, Tier.Wrecked => 0.68f, _ => 1f, }; if (1 == 0) { } float num2 = num; if (1 == 0) { } num = tier switch { Tier.Scratched => 0.02f, Tier.Damaged => 0.05f, Tier.Battered => 0.1f, Tier.Wrecked => 0.16f, _ => 0f, }; if (1 == 0) { } float num3 = num; if (1 == 0) { } num = tier switch { Tier.Scratched => 2f, Tier.Damaged => 4f, Tier.Battered => 7f, Tier.Wrecked => 11f, _ => 0f, }; if (1 == 0) { } float num4 = num; avatar.voiceChat.OverridePitch(num2, 0.4f, 0.4f, 0.6f, num3, num4); if (tier >= Tier.Battered) { avatar.voiceChat.OverrideVoiceDistortion(0.6f); } if (tier >= Tier.Wrecked) { avatar.voiceChat.OverrideVolumeStutter(0.6f); } if (SemiFunc.IsMultiplayer() && (Object)(object)avatar.photonView != (Object)null) { NetEvents.SendVoicePitch(avatar.photonView.ViewID, num2, num3, num4, 0.6f); } } } public static void CancelVoice(PlayerAvatar avatar) { if (!((Object)(object)avatar?.voiceChat == (Object)null)) { avatar.voiceChat.OverridePitchCancel(); if (SemiFunc.IsMultiplayer() && (Object)(object)avatar.photonView != (Object)null) { NetEvents.SendVoiceCancel(avatar.photonView.ViewID); } } } public static void SpawnSparks(PlayerAvatar avatar, int hitDamage) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (ConfigService.SparkParticlesEnabled() && !((Object)(object)avatar == (Object)null)) { Vector3 pos = ((Component)avatar).transform.position + Vector3.up; SpawnSparksAt(pos, hitDamage); if (SemiFunc.IsMultiplayer()) { NetEvents.SendSparkSpawn(pos, hitDamage); } } } public static void SpawnSparksAt(Vector3 pos, int hitDamage) { //IL_0019: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown if (ConfigService.SparkParticlesEnabled()) { GameObject val = new GameObject("BattleScars_Sparks"); val.transform.position = pos; GameObject val2 = val; ParticleSystem val3 = val2.AddComponent(); MainModule main = val3.main; ((MainModule)(ref main)).startLifetime = MinMaxCurve.op_Implicit(0.5f); ((MainModule)(ref main)).startSpeed = MinMaxCurve.op_Implicit(4f); ((MainModule)(ref main)).startSize = MinMaxCurve.op_Implicit(0.06f); ((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(1f, 0.7f, 0.1f)); ((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(0.8f); ((MainModule)(ref main)).maxParticles = 64; ((MainModule)(ref main)).duration = 0.2f; ((MainModule)(ref main)).loop = false; EmissionModule emission = val3.emission; ((EmissionModule)(ref emission)).rateOverTime = MinMaxCurve.op_Implicit(0f); int num = Mathf.Clamp(8 + hitDamage / 3, 8, 60); ((EmissionModule)(ref emission)).SetBurst(0, new Burst(0f, MinMaxCurve.op_Implicit((float)num))); ShapeModule shape = val3.shape; ((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)0; ((ShapeModule)(ref shape)).radius = 0.25f; Material material = new Material(Shader.Find("Sprites/Default")) { color = new Color(1f, 0.8f, 0.2f) }; ParticleSystemRenderer component = val2.GetComponent(); ((Renderer)component).material = material; component.renderMode = (ParticleSystemRenderMode)0; val3.Play(); Object.Destroy((Object)(object)val2, 1.5f); } } } public class NetEvents : MonoBehaviour, IOnEventCallback { public const byte EventVoicePitch = 188; public const byte EventSparkSpawn = 189; private static readonly RaiseEventOptions ToOthers = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; private static readonly SendOptions Reliable; private static readonly SendOptions Unreliable; private void OnEnable() { PhotonNetwork.AddCallbackTarget((object)this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); } public static void SendVoicePitch(int avatarViewID, float pitch, float oscillation, float oscSpeed, float duration) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { PhotonNetwork.RaiseEvent((byte)188, (object)new object[5] { avatarViewID, pitch, oscillation, oscSpeed, duration }, ToOthers, Reliable); } } public static void SendVoiceCancel(int avatarViewID) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { PhotonNetwork.RaiseEvent((byte)188, (object)new object[5] { avatarViewID, 1f, 0f, 0f, 0f }, ToOthers, Reliable); } } public static void SendSparkSpawn(Vector3 pos, int damage) { //IL_001c: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { PhotonNetwork.RaiseEvent((byte)189, (object)new object[4] { pos.x, pos.y, pos.z, damage }, ToOthers, Unreliable); } } public void OnEvent(EventData photonEvent) { switch (photonEvent.Code) { case 188: HandleVoicePitch(photonEvent.CustomData); break; case 189: HandleSparkSpawn(photonEvent.CustomData); break; } } private static void HandleVoicePitch(object? raw) { if (!(raw is object[] array) || array.Length < 5) { return; } object obj = array[0]; if (!(obj is int)) { return; } int num = (int)obj; if (1 == 0) { return; } obj = array[1]; if (!(obj is float)) { return; } float num2 = (float)obj; if (1 == 0) { return; } obj = array[2]; if (!(obj is float)) { return; } float num3 = (float)obj; if (1 == 0) { return; } obj = array[3]; if (!(obj is float)) { return; } float num4 = (float)obj; if (1 == 0) { return; } obj = array[4]; if (!(obj is float)) { return; } float num5 = (float)obj; if (1 == 0) { return; } PhotonView val = PhotonView.Find(num); PlayerAvatar val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.voiceChat == (Object)null)) { if (num5 <= 0.01f && Mathf.Approximately(num2, 1f)) { val2.voiceChat.OverridePitchCancel(); } else { val2.voiceChat.OverridePitch(num2, 0.4f, 0.4f, num5, num3, num4); } } } private static void HandleSparkSpawn(object? raw) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (!(raw is object[] array) || array.Length < 4) { return; } object obj = array[0]; if (!(obj is float)) { return; } float num = (float)obj; obj = array[1]; if (!(obj is float)) { return; } float num2 = (float)obj; obj = array[2]; float num3 = default(float); int num4; if (obj is float) { num3 = (float)obj; num4 = 1; } else { num4 = 0; } if (num4 == 0) { return; } obj = array[3]; if (obj is int) { int hitDamage = (int)obj; if (true) { Effects.SpawnSparksAt(new Vector3(num, num2, num3), hitDamage); } } } static NetEvents() { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0039: 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) SendOptions val = default(SendOptions); ((SendOptions)(ref val)).Reliability = true; Reliable = val; val = default(SendOptions); ((SendOptions)(ref val)).Reliability = false; Unreliable = val; } } internal static class PlayerLookup { public static PlayerAvatar? LocalAvatar() { foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item != (Object)null && item.isLocal) { return item; } } return null; } } public static class SaveBackup { private const int BackupsToKeep = 5; private static bool _ranThisSession; public static void TryBackupOnce(PlayerAvatar? avatar) { if (_ranThisSession || (Object)(object)avatar == (Object)null || string.IsNullOrWhiteSpace(avatar.playerName)) { return; } try { string text = Path.Combine(Application.persistentDataPath, "MetaSave.es3"); if (!File.Exists(text)) { _ranThisSession = true; return; } string text2 = Path.Combine(Paths.ConfigPath, "BattleScars", "backups", Sanitize(avatar.playerName)); Directory.CreateDirectory(text2); string stamp = DateTime.Now.ToString("yyyy-MM-dd_HHmmss"); string text3 = UniqueDestination(text2, stamp); File.Copy(text, text3, overwrite: false); BattleScars.Log.LogInfo((object)("[Backup] saved " + Path.GetFileName(text3))); Prune(text2); _ranThisSession = true; } catch (Exception ex) { BattleScars.Log.LogWarning((object)("[Backup] failed: " + ex.Message)); _ranThisSession = true; } } private static string Sanitize(string raw) { char[] invalid = Path.GetInvalidFileNameChars(); char[] value = raw.Select((char c) => invalid.Contains(c) ? '_' : c).ToArray(); string text = new string(value).Trim(); return (text.Length == 0) ? "unknown_player" : text; } private static string UniqueDestination(string dir, string stamp) { string text = Path.Combine(dir, stamp + "_MetaSave.es3"); if (!File.Exists(text)) { return text; } for (int i = 1; i < 1000; i++) { string text2 = Path.Combine(dir, $"{stamp}_{i:D2}_MetaSave.es3"); if (!File.Exists(text2)) { return text2; } } return Path.Combine(dir, $"{stamp}_{Guid.NewGuid():N}_MetaSave.es3"); } private static void Prune(string dir) { try { List list = Directory.GetFiles(dir, "*_MetaSave.es3").OrderByDescending(File.GetCreationTimeUtc).ToList(); for (int i = 5; i < list.Count; i++) { File.Delete(list[i]); } } catch { } } } public class ScreenOverlay : MonoBehaviour { private float _glitchTimer; private void Update() { if (!ConfigService.ScreenOverlayEnabled()) { return; } PlayerAvatar val = PlayerLookup.LocalAvatar(); if ((Object)(object)val == (Object)null || val.deadSet || val.isDisabled) { return; } Tier tier = ConfigService.TierForHealth(Driver.EffectiveHealthFor(val)); if (tier != 0) { _glitchTimer -= Time.deltaTime; if (!(_glitchTimer > 0f)) { _glitchTimer = GlitchInterval(val) * Random.Range(0.7f, 1.3f); FireGlitch(tier); } } } private static void FireGlitch(Tier tier) { CameraGlitch instance = CameraGlitch.Instance; if ((Object)(object)instance == (Object)null) { return; } float value = Random.value; if (tier >= Tier.Wrecked) { if (value < 0.65f) { instance.PlayLong(); } else { instance.PlayShort(); } } else if (tier >= Tier.Battered) { if (value < 0.45f) { instance.PlayLong(); } else if (value < 0.85f) { instance.PlayShort(); } else { instance.PlayTiny(); } } else if (tier >= Tier.Damaged) { if (value < 0.5f) { instance.PlayShort(); } else { instance.PlayTiny(); } } else { instance.PlayTiny(); } } private static float GlitchInterval(PlayerAvatar avatar) { if ((Object)(object)avatar.playerHealth == (Object)null) { return 6f; } int num = Mathf.Max(1, avatar.playerHealth.health); return Mathf.Clamp(0.5f + (float)num * 0.15f, 1f, 12f); } private void OnGUI() { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (!ConfigService.ScreenOverlayEnabled()) { return; } PlayerAvatar val = PlayerLookup.LocalAvatar(); if ((Object)(object)val == (Object)null) { return; } Tier tier = ConfigService.TierForHealth(Driver.EffectiveHealthFor(val)); if (tier != 0) { if (1 == 0) { } float num = tier switch { Tier.Scratched => 0.25f, Tier.Damaged => 0.5f, Tier.Battered => 0.75f, Tier.Wrecked => 1f, _ => 0f, }; if (1 == 0) { } float num2 = num; float num3 = 0.85f + 0.15f * Mathf.Sin(Time.time * (2f + num2 * 3f)); float num4 = Mathf.Lerp(0f, 0.2f, num2) * num3; Color color = GUI.color; GUI.color = new Color(0.6f, 0f, 0f, num4); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0); GUI.color = color; } } } public enum Tier { Healthy, Scratched, Damaged, Battered, Wrecked } } namespace BattleScars.Patches { [HarmonyPatch(typeof(PlayerCosmetics), "SetupCosmeticsLogic")] internal static class MenuAvatarCosmeticsPatch { [HarmonyPrefix] public static void Prefix(PlayerCosmetics __instance, ref int[] _cosmeticEquipped) { if (!ConfigService.CosmeticsEnabled() || (Object)(object)__instance == (Object)null || (Object)(object)__instance.playerAvatarVisuals == (Object)null || !__instance.playerAvatarVisuals.isMenuAvatar) { return; } PlayerAvatar val = PlayerLookup.LocalAvatar(); if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(val.steamID)) { return; } int currentHP = ((ConfigService.TestHealthOverride() >= 0) ? ConfigService.TestHealthOverride() : (((Object)(object)val.playerHealth != (Object)null) ? val.playerHealth.health : 100)); int num = ConfigService.CosmeticCountForHealth(currentHP); if (num <= 0) { return; } CosmeticPool pool = ConfigService.PoolForHealth(currentHP); bool flag = ConfigService.WreckedFaceActive(currentHP); List list = Cosmetics.PickForCount(val.steamID, num, pool); IReadOnlyList readOnlyList2; if (!flag) { IReadOnlyList readOnlyList = Array.Empty(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = Cosmetics.WreckedFaceIndices(); } IReadOnlyList readOnlyList3 = readOnlyList2; List list2 = new List(list.Count + readOnlyList3.Count); foreach (int item in readOnlyList3) { list2.Add(item); } foreach (int item2 in list) { if (!list2.Contains(item2)) { list2.Add(item2); } } List list3 = Cosmetics.Merge(MetaManager.instance?.cosmeticAssets, _cosmeticEquipped, list2); _cosmeticEquipped = list3.ToArray(); } } [HarmonyPatch(typeof(PlayerHealth), "Hurt")] internal static class PlayerHealthHurtPatch { [HarmonyPrefix] public static void Prefix(PlayerHealth __instance, out int __state) { __state = (((Object)(object)__instance != (Object)null) ? __instance.health : 0); } [HarmonyPostfix] public static void Postfix(PlayerHealth __instance, int __state) { if (!ConfigService.IsEnabled() || (Object)(object)__instance == (Object)null) { return; } int num = __state - __instance.health; if (num > 0) { PlayerAvatar component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && !string.IsNullOrEmpty(component.steamID)) { Effects.SpawnSparks(component, num); } } } } [HarmonyPatch(typeof(PlayerCosmetics), "SetupCosmetics")] internal static class SetupCosmeticsReassertPatch { [HarmonyPostfix] public static void Postfix(PlayerCosmetics __instance, bool _forced) { if (!CosmeticReassertGuard.IsInside && !_forced && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.playerAvatarVisuals == (Object)null) && !__instance.playerAvatarVisuals.isMenuAvatar) { PlayerAvatar playerAvatar = __instance.playerAvatarVisuals.playerAvatar; if (!((Object)(object)playerAvatar == (Object)null) && playerAvatar.isLocal) { Driver.Instance?.ReassertLocalCosmeticsImmediate(); } } } } [HarmonyPatch(typeof(StatsManager), "Start")] internal static class StatsManagerStartPatch { private static GameObject? _services; [HarmonyPostfix] public static void Postfix() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!((Object)(object)_services != (Object)null)) { _services = new GameObject("BattleScars_Services"); _services.AddComponent(); _services.AddComponent(); _services.AddComponent(); Object.DontDestroyOnLoad((Object)(object)_services); } } } } namespace BattleScars.Configuration { public enum RunMode { Off, VisualOnly, Full } internal static class PluginConfig { public const int RustyAtOrBelowHP = 75; public const int BandagesAtOrBelowHP = 60; public const int DamagedAtOrBelowHP = 50; public const int WreckedFaceAtOrBelowHP = 25; public const int CosmeticStepHP = 8; public const float SpeedNerfMax = 0.7f; public const float StaminaNerfMax = 0.45f; public const float ScreenOverlayMaxAlpha = 0.2f; public const string RustyAllowList = "Rusty"; public const string BandagesAllowList = "Bandages"; public const string DamagedAllowList = "Damaged,Cracks"; public const string WreckedFaceCosmetic = "Broken"; public static ConfigEntry Mode; public static ConfigEntry EnableCosmetics; public static ConfigEntry EnableSpeedNerf; public static ConfigEntry EnableStaminaNerf; public static ConfigEntry EnableVoiceEffects; public static ConfigEntry EnableSparkParticles; public static ConfigEntry EnableScreenOverlay; public static ConfigEntry TestHealth; public static void Init(ConfigFile config) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown Mode = config.Bind("General", "Mode", RunMode.VisualOnly, "Off: mod inactive. VisualOnly: scars + screen overlay + sparks, no nerfs. Full: everything per the Effects toggles below."); EnableCosmetics = config.Bind("Effects", "ForceBrokenCosmetics", true, "Force-apply broken cosmetics as damage stacks. Nothing is unlocked or saved."); EnableSpeedNerf = config.Bind("Effects", "SlowWhenHurt", true, "Reduce move and sprint speed as you take damage. Ignored in VisualOnly."); EnableStaminaNerf = config.Bind("Effects", "DrainStamina", true, "Cap max stamina as you take damage. Ignored in VisualOnly."); EnableVoiceEffects = config.Bind("Effects", "BreakVoice", false, "Pitch wobble and distortion on your voice. Currently not working reliably; default off until fixed."); EnableSparkParticles = config.Bind("Effects", "SpawnSparks", true, "Spark particles on hit. Other players need the mod to see them."); EnableScreenOverlay = config.Bind("Effects", "ScreenOverlay", true, "Red damage vignette on your own screen as you take damage."); TestHealth = config.Bind("Testing", "TestHealth", -1, new ConfigDescription("Preview a synthetic HP value. -1 disables. 0-100 forces that HP through the tier pipeline without touching real health or networked state. Numpad 0-9 in-game also drives this (0=off, 1=HP 1, 2=HP 20, etc).", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 100), Array.Empty())); } } }