using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; 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 System.Text.Json; using System.Text.Json.Serialization; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using CellMenu; using GTFO.API.Utilities; using GameData; using Gear; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Localization; using MTFO.API; using MTFO.Ext.PartialData; using MTFO.Ext.PartialData.JsonConverters; using Microsoft.CodeAnalysis; using Player; using TMPro; using UnityEngine; using UnityEngine.UI; using WeaponStatShower.Dependencies; using WeaponStatShower.ExtraDescription; using WeaponStatShower.ExtraDescription.Data; using WeaponStatShower.Json; using WeaponStatShower.Json.Converters.Utils; using WeaponStatShower.Patches; using WeaponStatShower.Utils; using WeaponStatShower.Utils.Extensions; using WeaponStatShower.Utils.Language; using WeaponStatShower.Utils.Language.Models; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("WeaponStatShower")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+75aa869cf995234f6c582c07759bee1e5af89b9a")] [assembly: AssemblyProduct("WeaponStatShower")] [assembly: AssemblyTitle("WeaponStatShower")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.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; } } } namespace WeaponStatShower { public static class BIEExtensions { public static ConfigEntry GetConfigEntry(this ConfigFile configFile, ConfigDefinition definition) { ConfigEntry result = default(ConfigEntry); if (!configFile.TryGetEntry(definition, ref result)) { throw new InvalidOperationException("Config entry has not been added yet."); } return result; } } [BepInPlugin("WeaponStatShower", "Weapon Stat Shower", "2.0.1")] [BepInProcess("GTFO.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class WeaponStatShowerPlugin : BasePlugin { private enum ShowStatSetting { False, True, Force } public enum StatsPosition { Last, First, Combined } internal const string ModName = "Weapon Stat Shower"; internal const string GUID = "WeaponStatShower"; private const string SectionMain = "Config"; private static readonly ConfigDefinition ConfigDefinition = new ConfigDefinition("Config", "Version"); private static readonly ConfigDefinition ConfigGameVersion = new ConfigDefinition("Config", "GameVersion"); private static Harmony? HarmonyInstance; private static readonly Dictionary RegisteredPatches = new Dictionary(); private static ConfigEntry _configLanguage = null; private static ConfigEntry _sleepersShown = null; private static ConfigEntry _showStats = null; private static ConfigEntry _statsPosition = null; public static WeaponStatShowerPlugin Instance { get; private set; } internal static bool ShowStats { get { if (_showStats.Value != ShowStatSetting.Force) { if (_showStats.Value == ShowStatSetting.True) { return !DescriptionDataManager.Current.GlobalSettings.PreferHideStats; } return false; } return true; } } internal static string SleepersShown => _sleepersShown.Value.Trim().ToUpper(); internal static LanguageEnum ConfigLanguage => _configLanguage.Value; internal static StatsPosition StatsLocation => _statsPosition.Value; public override void Load() { Instance = this; ((BasePlugin)this).Config.SaveOnConfigSet = true; LogInfo("STARTED"); DescriptionDataManager.Current.Init(); RegisterPatch(); BuildConfig(((BasePlugin)this).Config); } public static void RegisterPatch() where T : Patch, new() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (HarmonyInstance == null) { HarmonyInstance = new Harmony("WeaponStatShower"); } if (RegisteredPatches.TryGetValue(typeof(T), out Patch _)) { LogDebug("Ignoring duplicate patch: " + typeof(T).Name); return; } T val = new T { Harmony = HarmonyInstance }; val.Initialize(); if (val.Enabled) { LogInfo("Applying patch: " + val.Name); val.Execute(); } RegisteredPatches[typeof(T)] = val; } public static void LogDebug(object data) { ((BasePlugin)Instance).Log.LogDebug(data); } public static void LogError(object data) { ((BasePlugin)Instance).Log.LogError(data); } public static void LogFatal(object data) { ((BasePlugin)Instance).Log.LogFatal(data); } public static void LogInfo(object data) { ((BasePlugin)Instance).Log.LogInfo(data); } public static void LogMessage(object data) { ((BasePlugin)Instance).Log.LogMessage(data); } public static void LogWarning(object data) { ((BasePlugin)Instance).Log.LogWarning(data); } private static void BuildConfig(ConfigFile file) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown string text = "ShowStat"; _configLanguage = file.Bind(text, "Language", LanguageEnum.English, "Select the mod language."); _sleepersShown = file.Bind(text, "SleepersShown", "NONE", "Select which Sleepers are shown, seperated by a comma.\nAcceptable values: ALL, NONE, STRIKER, SHOOTER, SCOUT, BIG_STRIKER, BIG_SHOOTER, CHARGER, CHARGER_SCOUT"); _showStats = file.Bind(text, "ShowStats", ShowStatSetting.True, "Add a description tab with auto-generated weapon stats.\nForce will always create a tab, even if the rundown developer disables it."); _statsPosition = file.Bind(text, "StatsPosition", StatsPosition.Last, "Which tab to place the auto-generated weapon stats.\nCombined will combine it with the normal description, similar to older versions."); string? directoryName = Path.GetDirectoryName(file.ConfigFilePath); string fileName = Path.GetFileName(file.ConfigFilePath); LiveEdit.CreateListener(directoryName, fileName, false).FileChanged += (LiveEditEventHandler)delegate { file.Reload(); }; } } } namespace WeaponStatShower.Utils { internal static class ArchetypeUtil { internal static ArchetypeDataBlock? GetArchetypeDataBlock(GearIDRange idRange, uint categoryID, GearCategoryDataBlock gearCatBlock) { //IL_0007: 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_0010: Unknown result type (might be due to invalid IL or missing references) eWeaponFireMode val = (eWeaponFireMode)idRange.GetCompID((eGearComponent)1); if (categoryID != 12) { return GameDataBlockBase.GetBlock(GearBuilder.GetArchetypeID(gearCatBlock, val)); } return SentryGunInstance_Firing_Bullets.GetArchetypeDataForFireMode(val); } internal static ArchetypeDataBlock? GetMappedArchetypeDataBlock(GearIDRange idRange, uint categoryID, GearCategoryDataBlock gearCatBlock) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003e: 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) //IL_003c: 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) eWeaponFireMode val = (eWeaponFireMode)idRange.GetCompID((eGearComponent)1); bool flag = categoryID == 12; if (!flag) { val = (eWeaponFireMode)((val - 10) switch { 0 => 0, 1 => 2, 2 => 1, 3 => 0, _ => val, }); } if (!flag) { return GameDataBlockBase.GetBlock(GearBuilder.GetArchetypeID(gearCatBlock, val)); } return SentryGunInstance_Firing_Bullets.GetArchetypeDataForFireMode(val); } } [JsonConverter(typeof(LocaleTextConverter))] public struct LocaleText : IEquatable { public uint ID; public string RawText; public static readonly LocaleText Empty = new LocaleText(string.Empty); private readonly string TextFallback { get { if (ID != 0) { return Text.Get(ID); } return RawText; } } public LocaleText(LocalizedText baseText) { ID = 0u; RawText = string.Empty; RawText = LocalizedToText(baseText); ID = baseText.Id; } public LocaleText(string text) { ID = 0u; RawText = string.Empty; if (PartialData.TryGetGUID(text, out var guid)) { RawText = string.Empty; ID = guid; } else { RawText = text; ID = 0u; } } public LocaleText(uint id) { ID = 0u; RawText = string.Empty; RawText = string.Empty; ID = id; } public readonly LocalizedText ToLocalizedText() { //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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown return new LocalizedText { Id = ID, UntranslatedText = TextFallback }; } public override readonly string ToString() { return TextFallback; } public static string LocalizedToText(LocalizedText text) { if (!text.HasTranslation) { return text.UntranslatedText; } return Text.Get(text.Id); } public static explicit operator LocaleText(LocalizedText localizedText) { return new LocaleText(localizedText); } public static explicit operator LocaleText(string text) { return new LocaleText(text); } public static implicit operator LocalizedText(LocaleText localeText) { return localeText.ToLocalizedText(); } public static implicit operator string(LocaleText localeText) { return localeText.ToString(); } public readonly bool Equals(LocaleText other) { if (ID == other.ID) { return string.Equals(RawText, other.RawText, StringComparison.Ordinal); } return false; } public override readonly bool Equals(object? obj) { if (obj is LocaleText other) { return Equals(other); } return false; } public override readonly int GetHashCode() { return HashCode.Combine(ID, RawText); } public static bool operator ==(LocaleText left, LocaleText right) { return left.Equals(right); } public static bool operator !=(LocaleText left, LocaleText right) { return !(left == right); } } internal class SleepersDatas { private readonly struct EnemyData { public readonly float Health; public readonly float HeadMultiplier; public readonly float BackMultiplier; public readonly bool IsArmored; public EnemyData(float health, float headMultiplier, float backMultiplier, bool isArmored) { Health = health; HeadMultiplier = headMultiplier; BackMultiplier = backMultiplier; IsArmored = isArmored; } } private readonly Dictionary _enemyDatas = new Dictionary(); private readonly SleepersLanguageModel sleepersLanguageDatas; public SleepersDatas(string[] activatedSleepers, SleepersLanguageModel sleepersLanguageDatas) { this.sleepersLanguageDatas = sleepersLanguageDatas; for (int i = 0; i < activatedSleepers.Length; i++) { string text = activatedSleepers[i].Trim(); switch (text) { case "DETAILS_ONLY": case "DESCRIPTION_ONLY": case "NONE": _enemyDatas.Clear(); return; case "ALL": _enemyDatas.Clear(); _enemyDatas.TryAdd(sleepersLanguageDatas.striker, new EnemyData(20f, 3f, 2f, isArmored: false)); _enemyDatas.TryAdd(sleepersLanguageDatas.shooter, new EnemyData(30f, 5f, 2f, isArmored: false)); _enemyDatas.TryAdd(sleepersLanguageDatas.scout, new EnemyData(42f, 3f, 2f, isArmored: false)); _enemyDatas.TryAdd(sleepersLanguageDatas.bigStriker, new EnemyData(120f, 1.5f, 2f, isArmored: false)); _enemyDatas.TryAdd(sleepersLanguageDatas.bigShooter, new EnemyData(150f, 2f, 2f, isArmored: false)); _enemyDatas.TryAdd(sleepersLanguageDatas.charger, new EnemyData(30f, 1f, 2f, isArmored: true)); _enemyDatas.TryAdd(sleepersLanguageDatas.chargerScout, new EnemyData(60f, 1f, 2f, isArmored: true)); return; case "STRIKER": _enemyDatas.TryAdd(sleepersLanguageDatas.striker, new EnemyData(20f, 3f, 2f, isArmored: false)); break; case "SHOOTER": _enemyDatas.TryAdd(sleepersLanguageDatas.shooter, new EnemyData(30f, 5f, 2f, isArmored: false)); break; case "SCOUT": _enemyDatas.TryAdd(sleepersLanguageDatas.scout, new EnemyData(42f, 3f, 2f, isArmored: false)); break; case "BIG_STRIKER": _enemyDatas.TryAdd(sleepersLanguageDatas.bigStriker, new EnemyData(120f, 1.5f, 2f, isArmored: false)); break; case "BIG_SHOOTER": _enemyDatas.TryAdd(sleepersLanguageDatas.bigShooter, new EnemyData(150f, 2f, 2f, isArmored: false)); break; case "CHARGER": _enemyDatas.TryAdd(sleepersLanguageDatas.charger, new EnemyData(30f, 1f, 2f, isArmored: true)); break; case "CHARGER_SCOUT": _enemyDatas.TryAdd(sleepersLanguageDatas.chargerScout, new EnemyData(60f, 1f, 2f, isArmored: true)); break; default: WeaponStatShowerPlugin.LogWarning("You inserted an incorrect value in the config: " + text); break; } } } public string VerboseKill(ArchetypeDataBlock archetypeDB) { float damage = archetypeDB.Damage * (float)((archetypeDB.ShotgunBulletCount <= 0) ? 1 : archetypeDB.ShotgunBulletCount); float precisionDamageMulti = archetypeDB.PrecisionDamageMulti; return BuildKillString(precisionDamageMulti, (string _, EnemyData __) => damage); } internal string? VerboseKill(MeleeArchetypeDataBlock meleeArchetypeDB) { float baseDamage = meleeArchetypeDB.ChargedAttackDamage * meleeArchetypeDB.ChargedSleeperMulti; float chargedPrecisionMulti = meleeArchetypeDB.ChargedPrecisionMulti; return BuildKillString(chargedPrecisionMulti, delegate(string enemyName, EnemyData _) { float num = baseDamage; if (enemyName.Contains("SCOUT") || enemyName.Contains("哨兵") || enemyName.Contains("黑触")) { num /= meleeArchetypeDB.ChargedSleeperMulti; } return num; }); } private string BuildKillString(float prcnMult, Func getDamage) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; int num2 = 0; int num3 = _enemyDatas.Count - 1; foreach (KeyValuePair enemyData2 in _enemyDatas) { enemyData2.Deconstruct(out var key, out var value); string text = key; EnemyData enemyData = value; float damage = getDamage(text, enemyData); List list = new List(4); if (canKillOnOccipit(damage, prcnMult, enemyData)) { list.Add(sleepersLanguageDatas.occipit); } if (canKillOnHead(damage, prcnMult, enemyData)) { list.Add(sleepersLanguageDatas.head); } if (canKillOnBack(damage, enemyData)) { list.Add(sleepersLanguageDatas.back); } if (canKillOnChest(damage, enemyData)) { list.Add(sleepersLanguageDatas.chest); } if (list.Count > 0) { if (num % 2 == 1) { stringBuilder.Append(" | "); } list.Reverse(); stringBuilder.Append(text + ": [" + string.Join(",", list) + "]"); if (num++ % 2 == 1 && num2 != num3) { stringBuilder.AppendLine(); } } num2++; } if (num > 1 && stringBuilder.Length > 0) { if (stringBuilder[stringBuilder.Length - 1] != '\n') { stringBuilder.AppendLine(); } } return stringBuilder.ToString(); } private bool canKillOnChest(float damage, EnemyData data) { return damage >= data.Health; } private bool canKillOnBack(float damage, EnemyData data) { return damage * data.BackMultiplier >= data.Health; } private bool canKillOnHead(float damage, float prcnMultiplier, EnemyData data) { if (isArmored(data)) { return damage * data.HeadMultiplier >= data.Health; } return damage * prcnMultiplier * data.HeadMultiplier >= data.Health; } private bool canKillOnOccipit(float damage, float prcnMultiplier, EnemyData data) { if (isArmored(data)) { return damage * data.BackMultiplier * data.HeadMultiplier >= data.Health; } return damage * prcnMultiplier * data.BackMultiplier * data.HeadMultiplier >= data.Health; } private bool isArmored(EnemyData data) { return data.IsArmored; } } internal class WeaponDescriptionBuilder { private PlayerDataBlock _playerDB; private GearIDRange _idRange; private uint _categoryID; private GearCategoryDataBlock _gearCategoryDB; private ItemDataBlock _itemDB; private LanguageDatas _languageDatas; private LanguageEnum _language; private SleepersDatas _sleepersDatas; private string _lastSleepersDatas; private readonly StringBuilder _strBuilder = new StringBuilder(); private const string DIVIDER = " | "; private const string CLOSE_COLOR_TAG = ""; private const string SHOTGUN_PREFAB = "Assets/AssetPrefabs/Items/Weapons/GearSetup/ShotgunWeaponFirstPerson.prefab"; internal void Inizialize(GearIDRange idRange, PlayerDataBlock playerDB, LanguageEnum language) { _idRange = idRange; _playerDB = playerDB; _categoryID = idRange.GetCompID((eGearComponent)2); _gearCategoryDB = GameDataBlockBase.GetBlock(_categoryID); _itemDB = GameDataBlockBase.GetBlock(_gearCategoryDB.BaseItem); bool sleepersDatas = DeserializeLanguageJson(language); SetSleepersDatas(sleepersDatas); } private bool DeserializeLanguageJson(LanguageEnum language) { if (_language == language && _languageDatas != null) { return false; } LanguageDatasClass languageDatasClass = JsonSerializer.Deserialize(LocalizedString.JsonString); _languageDatas = (language.Equals(LanguageEnum.English) ? languageDatasClass.english : languageDatasClass.chinese); _language = language; return true; } private void SetSleepersDatas(bool force) { if (!(_lastSleepersDatas == WeaponStatShowerPlugin.SleepersShown) || force) { _lastSleepersDatas = WeaponStatShowerPlugin.SleepersShown; string[] array = _lastSleepersDatas.Split(','); if (array[0].Trim().Length == 0) { WeaponStatShowerPlugin.LogWarning("Empty String in the config file, applying Default values"); array = new string[1] { "ALL" }; } _sleepersDatas = new SleepersDatas(array, _languageDatas.sleepers); } } public string DescriptionFormatter() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if ((int)_itemDB.inventorySlot == 10) { MeleeArchetypeDataBlock block = GameDataBlockBase.GetBlock(GearBuilder.GetMeleeArchetypeID(_gearCategoryDB)); return VerboseDescriptionFormatter(block) + GetFormatedWeaponStats(block, _itemDB); } var (val, flag) = GetRangedArchetype(); if (val == null) { return string.Empty; } return string.Concat(str1: GetFormatedWeaponStats(val, (!flag) ? (_itemDB.FirstPersonPrefabs != null && _itemDB.FirstPersonPrefabs.Contains("Assets/AssetPrefabs/Items/Weapons/GearSetup/ShotgunWeaponFirstPerson.prefab")) : (_idRange.GetCompID((eGearComponent)1) == 13), flag), str0: VerboseDescriptionFormatter(val, flag)); } internal string FireRateFormatter(string gearPublicName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if ((int)_itemDB.inventorySlot == 10) { MeleeArchetypeDataBlock block = GameDataBlockBase.GetBlock(GearBuilder.GetMeleeArchetypeID(_gearCategoryDB)); MeleeLanguageModel melee = _languageDatas.melee; return ((GameDataBlockBase)(object)block).persistentID switch { 1u => melee.hammer, 2u => melee.knife, 3u => melee.spear, 4u => melee.bat, _ => gearPublicName, }; } ArchetypeDataBlock item = GetRangedArchetype().Item1; if (item == null) { return gearPublicName; } return VerbosePublicNameFireMode(item); } private (ArchetypeDataBlock?, bool) GetRangedArchetype() { bool item = _categoryID == 12; return (ArchetypeUtil.GetMappedArchetypeDataBlock(_idRange, _categoryID, _gearCategoryDB), item); } private string VerbosePublicNameFireMode(ArchetypeDataBlock archetypeDB) { //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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected I4, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) FiremodeLanguageModel firemode = _languageDatas.firemode; eWeaponFireMode fireMode = archetypeDB.FireMode; _strBuilder.Clear(); switch ((int)fireMode) { case 2: case 11: { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder6 = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 4, strBuilder); handler.AppendFormatted(firemode.fullA); handler.AppendLiteral(" (<#12FF50>"); handler.AppendFormatted(_languageDatas.rateOfFire); handler.AppendLiteral(" "); handler.AppendFormatted(GetRateOfFire(archetypeDB, fireMode)); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder6.Append(ref handler); break; } case 0: case 10: { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder5 = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 4, strBuilder); handler.AppendFormatted(firemode.semiA); handler.AppendLiteral(" (<#12FF50>"); handler.AppendFormatted(_languageDatas.rateOfFire); handler.AppendLiteral(" "); handler.AppendFormatted(GetRateOfFire(archetypeDB, fireMode)); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder5.Append(ref handler); break; } case 1: case 12: if (archetypeDB.BurstShotCount != 1) { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder3 = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(23, 7, strBuilder); handler.AppendFormatted(firemode.burst); handler.AppendLiteral(" (<#704dfa>#"); handler.AppendFormatted(archetypeDB.BurstShotCount); handler.AppendFormatted(""); handler.AppendFormatted(" | "); handler.AppendLiteral("<#12FF50>"); handler.AppendFormatted(_languageDatas.rateOfFire); handler.AppendLiteral(" "); handler.AppendFormatted(GetRateOfFire(archetypeDB, fireMode)); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder3.Append(ref handler); } else { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder4 = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 4, strBuilder); handler.AppendFormatted(firemode.burst); handler.AppendLiteral(" (<#12FF50>"); handler.AppendFormatted(_languageDatas.rateOfFire); handler.AppendLiteral(" "); handler.AppendFormatted(GetRateOfFire(archetypeDB, (eWeaponFireMode)0)); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder4.Append(ref handler); } break; case 3: { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder2 = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(28, 3, strBuilder); handler.AppendLiteral("S-Burst (<#12FF50>#"); handler.AppendFormatted(archetypeDB.BurstShotCount); handler.AppendLiteral(" every "); handler.AppendFormatted(archetypeDB.SpecialSemiBurstCountTimeout); handler.AppendLiteral("'"); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder2.Append(ref handler); break; } case 13: { StringBuilder strBuilder = _strBuilder; StringBuilder stringBuilder = strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 4, strBuilder); handler.AppendFormatted(firemode.shotgunSentry); handler.AppendLiteral(" (<#12FF50>"); handler.AppendFormatted(_languageDatas.rateOfFire); handler.AppendLiteral(" "); handler.AppendFormatted(GetRateOfFire(archetypeDB, fireMode)); handler.AppendFormatted(""); handler.AppendLiteral(")"); stringBuilder.Append(ref handler); break; } default: WeaponStatShowerPlugin.LogError("FireMode not found"); break; } return _strBuilder.ToString(); } private string VerboseDescriptionFormatter(ArchetypeDataBlock archetypeDB, bool isSentry) { _strBuilder.Clear(); float specialChargetupTime = archetypeDB.SpecialChargetupTime; if (specialChargetupTime > 0f) { string value = (((double)specialChargetupTime > 0.4) ? _languageDatas.longChargeUp : _languageDatas.shortChargeUp); StringBuilder strBuilder = _strBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, strBuilder); handler.AppendFormatted(value); handler.AppendLiteral(" ("); handler.AppendFormatted(FormatFloat(specialChargetupTime, 2)); handler.AppendLiteral(")"); strBuilder.AppendLine(ref handler); } if (_strBuilder.Length != 0) { return _strBuilder.ToString() + "\n"; } return string.Empty; } private string VerboseDescriptionFormatter(MeleeArchetypeDataBlock meleeArchetypeDB) { _strBuilder.Clear(); if ((double)meleeArchetypeDB.CameraDamageRayLength < 1.76) { _strBuilder.AppendLine(_languageDatas.melee.shortRange); } else if ((double)meleeArchetypeDB.CameraDamageRayLength < 2.5) { _strBuilder.AppendLine(_languageDatas.melee.mediumRange); } else { _strBuilder.AppendLine(_languageDatas.melee.longRange); } _strBuilder.Append(meleeArchetypeDB.CanHitMultipleEnemies ? (_languageDatas.melee.canPierce + "\n") : string.Empty); if (_strBuilder.Length != 0) { return _strBuilder.ToString(); } return string.Empty; } private string GetFormatedWeaponStats(ArchetypeDataBlock archetypeDB, bool isShotgun, bool isSentry = false) { //IL_01cf: Unknown result type (might be due to invalid IL or missing references) if (archetypeDB == null) { return string.Empty; } int count = 0; int dividerThreshold = ((_language != LanguageEnum.English) ? 3 : 4); _strBuilder.Clear(); string value = FormatFloat(archetypeDB.Damage, 2) + (isShotgun ? $"(x{archetypeDB.ShotgunBulletCount})" : ""); AppendStat(ref count, "<#9D2929>", _languageDatas.damage, value); AppendStatIf(ref count, Divider, !isSentry, "", _languageDatas.clip, archetypeDB.DefaultClipSize.ToString()); Divider(); AppendStat(ref count, "<#FFD306>", _languageDatas.maxAmmo, GetTotalAmmo(archetypeDB, _itemDB, isSentry).ToString()); AppendStatIf(ref count, Divider, !isSentry, "<#C0FF00>", _languageDatas.reload, FormatFloat(archetypeDB.DefaultReloadTime, 2).ToString()); AppendStatIf(ref count, Divider, archetypeDB.PrecisionDamageMulti != 1f, "<#18A4A9>", _languageDatas.precision, FormatFloat(archetypeDB.PrecisionDamageMulti, 2).ToString()); Divider(); AppendStat(ref count, "<#6764de>", _languageDatas.falloff, (int)archetypeDB.DamageFalloff.x + "m"); AppendStatIf(ref count, Divider, archetypeDB.StaggerDamageMulti != 1f, "", _languageDatas.stagger, FormatFloat(archetypeDB.StaggerDamageMulti, 2).ToString()); AppendStatIf(ref count, Divider, archetypeDB.HipFireSpread != 0f && !isShotgun, "<#cc9347>", _languageDatas.hipSpread, FormatFloat(archetypeDB.HipFireSpread, 2).ToString()); AppendStatIf(ref count, Divider, archetypeDB.AimSpread != 0f && !isShotgun, "<#e6583c>", _languageDatas.aimDSpread, FormatFloat(archetypeDB.AimSpread, 2).ToString()); AppendStatIf(ref count, Divider, (float)(archetypeDB.ShotgunBulletSpread + archetypeDB.ShotgunConeSize) != 0f && isShotgun, "<#e6583c>", _languageDatas.spread, FormatFloat(archetypeDB.ShotgunBulletSpread + archetypeDB.ShotgunConeSize, 2).ToString()); AppendStatIf(ref count, Divider, archetypeDB.PiercingBullets, "<#097345>", _languageDatas.pierceCount, archetypeDB.PiercingDamageCountLimit.ToString()); _strBuilder.AppendLine(); _strBuilder.Append(_sleepersDatas.VerboseKill(archetypeDB)); return _strBuilder.ToString(); void Divider() { if (count >= dividerThreshold) { _strBuilder.AppendLine(); count = 0; } else if (count > 0) { _strBuilder.Append(" | "); } } } private string GetFormatedWeaponStats(MeleeArchetypeDataBlock meleeArchetypeDB, ItemDataBlock itemDB) { if (meleeArchetypeDB == null) { return string.Empty; } int count = 0; MeleeLanguageModel melee = _languageDatas.melee; _strBuilder.Clear(); _strBuilder.AppendLine(); AppendStat(ref count, "<#9D2929>", _languageDatas.damage + "." + melee.light, meleeArchetypeDB.LightAttackDamage.ToString()); Divider(); AppendStat(ref count, "", _languageDatas.damage + "." + melee.heavy, meleeArchetypeDB.ChargedAttackDamage.ToString()); AppendStatIf(ref count, Divider, !meleeArchetypeDB.AllowRunningWhenCharging, "<#FFD306>", melee.canRunWhileCharging); AppendStatIf(ref count, Divider, meleeArchetypeDB.LightStaggerMulti != 1f, "<#C0FF00>", _languageDatas.stagger + "." + melee.light, meleeArchetypeDB.LightStaggerMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.ChargedStaggerMulti != 1f, "", _languageDatas.stagger + "." + melee.heavy, meleeArchetypeDB.ChargedStaggerMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.LightPrecisionMulti != 1f, "<#004E2C>", _languageDatas.precision + "." + melee.light, meleeArchetypeDB.LightPrecisionMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.ChargedPrecisionMulti != 1f, "<#55022B>", _languageDatas.precision + "." + melee.heavy, meleeArchetypeDB.ChargedPrecisionMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.LightSleeperMulti != 1f, "<#A918A7>", melee.sleepingEnemiesMultiplier + "." + melee.light, meleeArchetypeDB.LightSleeperMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.ChargedSleeperMulti != 1f, "<#025531>", melee.sleepingEnemiesMultiplier + "." + melee.heavy, meleeArchetypeDB.ChargedSleeperMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.LightBackstabberMulti != 1f, "<#18A4A9>", melee.backstabMultiplier + "." + melee.light, meleeArchetypeDB.LightBackstabberMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.ChargedBackstabberMulti != 1f, "<#75A2AA>", melee.backstabMultiplier + "." + melee.heavy, meleeArchetypeDB.ChargedBackstabberMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.LightEnvironmentMulti != 1f, "<#18A4A9>", melee.environmentMultiplier + "." + melee.light, meleeArchetypeDB.LightEnvironmentMulti.ToString()); AppendStatIf(ref count, Divider, meleeArchetypeDB.ChargedEnvironmentMulti != 1f, "<#75A2AA>", melee.environmentMultiplier + "." + melee.heavy, meleeArchetypeDB.ChargedEnvironmentMulti.ToString()); _strBuilder.AppendLine(); _strBuilder.Append(_sleepersDatas.VerboseKill(meleeArchetypeDB) ?? string.Empty); return _strBuilder.ToString(); void Divider() { if (count >= 3) { _strBuilder.AppendLine(); count = 0; } else if (count > 0) { _strBuilder.Append(" | "); } } } private void AppendStatIf(ref int count, Action divider, bool condition, string color, string label, string value = "") { if (condition) { divider(); AppendStat(ref count, color, label, value); } } private void AppendStat(ref int count, string color, string label, string value = "") { _strBuilder.Append(color); _strBuilder.Append(label); if (!string.IsNullOrEmpty(value)) { _strBuilder.Append(' '); _strBuilder.Append(value); } _strBuilder.Append(""); count++; } private static float FormatFloat(float value, int v) { return (float)Math.Round((decimal)value, v); } private int GetAmmoMax(ItemDataBlock itemDataBlock) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown AmmoType ammoTypeFromSlot = PlayerAmmoStorage.GetAmmoTypeFromSlot(itemDataBlock.inventorySlot); return (int)ammoTypeFromSlot switch { 0 => _playerDB.AmmoStandardMaxCap, 1 => _playerDB.AmmoSpecialMaxCap, 2 => _playerDB.AmmoClassMaxCap, 5 => itemDataBlock.ConsumableAmmoMax, _ => -1, }; } private int GetTotalAmmo(ArchetypeDataBlock archetypeDB, ItemDataBlock itemDB, bool isSentry = false) { int ammoMax = GetAmmoMax(itemDB); float num = archetypeDB.CostOfBullet; if (isSentry) { num *= itemDB.ClassAmmoCostFactor; if ((float)archetypeDB.ShotgunBulletCount > 0f) { num *= (float)archetypeDB.ShotgunBulletCount; } } int num2 = (int)((float)ammoMax / num); if (!isSentry) { return num2 + archetypeDB.DefaultClipSize; } return num2; } private string GetRateOfFire(ArchetypeDataBlock archetypeDB, eWeaponFireMode fireMode) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected I4, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected I4, but got Unknown float value = -1f; switch ((int)fireMode) { default: switch (fireMode - 11) { case 0: break; case 2: goto IL_003e; case 1: goto IL_0054; default: goto end_IL_0007; } goto case 2; case 2: value = 1f / archetypeDB.ShotDelay; break; case 0: goto IL_003e; case 1: goto IL_0054; IL_0054: value = 1f / (archetypeDB.BurstDelay + archetypeDB.SpecialChargetupTime + archetypeDB.ShotDelay * (float)(archetypeDB.BurstShotCount - 1)) * (float)archetypeDB.BurstShotCount; break; IL_003e: value = 1f / (archetypeDB.ShotDelay + archetypeDB.SpecialChargetupTime); break; end_IL_0007: break; } return FormatFloat(value, 1).ToString(); } } } namespace WeaponStatShower.Utils.Language { public class LanguageDatas { public FiremodeLanguageModel firemode { get; set; } = new FiremodeLanguageModel(); public MeleeLanguageModel melee { get; set; } = new MeleeLanguageModel(); public SleepersLanguageModel sleepers { get; set; } = new SleepersLanguageModel(); public string damage { get; set; } = string.Empty; public string clip { get; set; } = string.Empty; public string maxAmmo { get; set; } = string.Empty; public string falloff { get; set; } = string.Empty; public string reload { get; set; } = string.Empty; public string stagger { get; set; } = string.Empty; public string precision { get; set; } = string.Empty; public string pierceCount { get; set; } = string.Empty; public string rateOfFire { get; set; } = string.Empty; public string aimDSpread { get; set; } = string.Empty; public string hipSpread { get; set; } = string.Empty; public string spread { get; set; } = string.Empty; public string deployable { get; set; } = string.Empty; public string longChargeUp { get; set; } = string.Empty; public string shortChargeUp { get; set; } = string.Empty; } internal class LanguageDatasClass { public LanguageDatas english { get; set; } = new LanguageDatas(); public LanguageDatas chinese { get; set; } = new LanguageDatas(); } internal enum LanguageEnum { English, Chinese } internal class LocalizedString { public static readonly string JsonString = "{\r\n\t\t\t\"english\": {\r\n\t\t\t\t\"firemode\": {\r\n\t\t\t\t\t\"fullA\": \"Full-A\",\r\n\t\t\t\t\t\"semiA\": \"Semi-A\",\r\n\t\t\t\t\t\"burst\": \"Burst\",\r\n\t\t\t\t\t\"shotgunSentry\": \"Shotgun-S\"\r\n\t\t\t\t},\r\n\t\t\t\t\"melee\": {\r\n\t\t\t\t\t\"light\": \"Lgt\",\r\n\t\t\t\t\t\"heavy\": \"Hvy\",\r\n\t\t\t\t\t\"canRunWhileCharging\": \"Can't run\",\r\n\t\t\t\t\t\"sleepingEnemiesMultiplier\": \"Slp\",\r\n\t\t\t\t\t\"backstabMultiplier\": \"Bak\",\r\n\t\t\t\t\t\"environmentMultiplier\": \"Env\",\r\n\t\t\t\t\t\"canPierce\": \"Piercing\",\r\n\t\t\t\t\t\"shortRange\": \"Short Range\",\r\n\t\t\t\t\t\"mediumRange\": \"Medium Range\",\r\n\t\t\t\t\t\"longRange\": \"Long Range\",\r\n\t\t\t\t\t\"hammer\": \"Hammer - Balanced\",\r\n\t\t\t\t\t\"knife\": \"Knife - Fast\",\r\n\t\t\t\t\t\"bat\": \"Bat - Fast\",\r\n\t\t\t\t\t\"spear\": \"Spear - Slow\"\r\n\t\t\t\t},\r\n\t\t\t\t\"sleepers\": {\r\n\t\t\t\t\t\"striker\": \"STRK\",\r\n\t\t\t\t\t\"shooter\": \"SHTR\",\r\n\t\t\t\t\t\"scout\": \"SCOUT\",\r\n\t\t\t\t\t\"bigStriker\": \"B-STRK\",\r\n\t\t\t\t\t\"bigShooter\": \"B-SHTR\",\r\n\t\t\t\t\t\"charger\": \"CHRG\",\r\n\t\t\t\t\t\"chargerScout\": \"C-SCOUT\",\r\n\t\t\t\t\t\"occipit\": \"o\",\r\n\t\t\t\t\t\"head\": \"h\",\r\n\t\t\t\t\t\"back\": \"b\",\r\n\t\t\t\t\t\"chest\": \"c\"\r\n\t\t\t\t},\r\n\t\t\t\t\"damage\": \"Dmg\",\r\n\t\t\t\t\"clip\": \"Clp\",\r\n\t\t\t\t\"maxAmmo\": \"Max\",\r\n\t\t\t\t\"falloff\": \"Dist\",\r\n\t\t\t\t\"reload\": \"Rld\",\r\n\t\t\t\t\"stagger\": \"Stgr\",\r\n\t\t\t\t\"precision\": \"Prcn\",\r\n\t\t\t\t\"pierceCount\": \"Pierc\",\r\n\t\t\t\t\"rateOfFire\": \"RoF\",\r\n\t\t\t\t\"aimDSpread\": \"ADS\",\r\n\t\t\t\t\"hipSpread\": \"HIP\",\r\n\t\t\t\t\"spread\": \"SPRD\",\r\n\t\t\t\t\"deployable\": \"Deployable\",\r\n\t\t\t\t\"longChargeUp\": \"Long Charge-up\",\r\n\t\t\t\t\"shortChargeUp\": \"short Charge-up\"\r\n\t\t\t},\r\n\r\n\t\t\t\"chinese\": {\r\n\t\t\t\t\"firemode\": {\r\n\t\t\t\t\t\"fullA\": \"全自动\",\r\n\t\t\t\t\t\"semiA\": \"半自动\",\r\n\t\t\t\t\t\"burst\": \"连发\",\r\n\t\t\t\t\t\"shotgunSentry\": \"半自动霰弹射击\"\r\n\t\t\t\t},\r\n\t\t\t\t\"melee\": {\r\n\t\t\t\t\t\"light\": \"轻击\",\r\n\t\t\t\t\t\"heavy\": \"重击\",\r\n\t\t\t\t\t\"canRunWhileCharging\": \"不能冲刺\",\r\n\t\t\t\t\t\"sleepingEnemiesMultiplier\": \"沉睡倍率\",\r\n\t\t\t\t\t\"backstabMultiplier\": \"背后捅刀\",\r\n\t\t\t\t\t\"environmentMultiplier\": \"环境倍率\",\r\n\t\t\t\t\t\"canPierce\": \"穿刺\",\r\n\t\t\t\t\t\"shortRange\": \"短距离\",\r\n\t\t\t\t\t\"mediumRange\": \"中距离\",\r\n\t\t\t\t\t\"longRange\": \"长距离\",\r\n\t\t\t\t\t\"hammer\": \"大锤 - 均衡\",\r\n\t\t\t\t\t\"knife\": \"刀 - 快速\",\r\n\t\t\t\t\t\"bat\": \"棍棒 - 快速\",\r\n\t\t\t\t\t\"spear\": \"矛 - 慢速\"\r\n\t\t\t\t},\r\n\t\t\t\t\"sleepers\": {\r\n\t\t\t\t\t\"striker\": \"前锋\",\r\n\t\t\t\t\t\"shooter\": \"射手\",\r\n\t\t\t\t\t\"scout\": \"哨兵\",\r\n\t\t\t\t\t\"bigStriker\": \"大壮\",\r\n\t\t\t\t\t\"bigShooter\": \"大姐\",\r\n\t\t\t\t\t\"charger\": \"黑刺\",\r\n\t\t\t\t\t\"chargerScout\": \"黑触\",\r\n\t\t\t\t\t\"occipit\": \"后脑\",\r\n\t\t\t\t\t\"head\": \"头\",\r\n\t\t\t\t\t\"back\": \"背\",\r\n\t\t\t\t\t\"chest\": \"胸\"\r\n\t\t\t\t},\r\n\r\n\t\t\t\t\"damage\": \"伤害\",\r\n\t\t\t\t\"clip\": \"弹容量\",\r\n\t\t\t\t\"maxAmmo\": \"总弹量\",\r\n\t\t\t\t\"falloff\": \"衰减距离\",\r\n\t\t\t\t\"reload\": \"换弹时间\",\r\n\t\t\t\t\"stagger\": \"硬直倍率\",\r\n\t\t\t\t\"precision\": \"精准倍率\",\r\n\t\t\t\t\"pierceCount\": \"穿透\",\r\n\t\t\t\t\"rateOfFire\": \"射速\",\r\n\t\t\t\t\"aimDSpread\": \"倍镜\",\r\n\t\t\t\t\"hipSpread\": \"后坐力\",\r\n\t\t\t\t\"spread\": \"点差\",\r\n\t\t\t\t\"deployable\": \"可部署\",\r\n\t\t\t\t\"longChargeUp\": \"长蓄力\",\r\n\t\t\t\t\"shortChargeUp\": \"短蓄力\"\r\n\t\t\t}\r\n\t\t}"; } } namespace WeaponStatShower.Utils.Language.Models { public class FiremodeLanguageModel { public string fullA { get; set; } = string.Empty; public string semiA { get; set; } = string.Empty; public string burst { get; set; } = string.Empty; public string shotgunSentry { get; set; } = string.Empty; } public class MeleeLanguageModel { public string light { get; set; } = string.Empty; public string heavy { get; set; } = string.Empty; public string canRunWhileCharging { get; set; } = string.Empty; public string sleepingEnemiesMultiplier { get; set; } = string.Empty; public string backstabMultiplier { get; set; } = string.Empty; public string environmentMultiplier { get; set; } = string.Empty; public string canPierce { get; set; } = string.Empty; public string shortRange { get; set; } = string.Empty; public string mediumRange { get; set; } = string.Empty; public string longRange { get; set; } = string.Empty; public string hammer { get; set; } = string.Empty; public string knife { get; set; } = string.Empty; public string bat { get; set; } = string.Empty; public string spear { get; set; } = string.Empty; } public class SleepersLanguageModel { public string striker { get; set; } = string.Empty; public string shooter { get; set; } = string.Empty; public string scout { get; set; } = string.Empty; public string bigStriker { get; set; } = string.Empty; public string bigShooter { get; set; } = string.Empty; public string charger { get; set; } = string.Empty; public string chargerScout { get; set; } = string.Empty; public string occipit { get; set; } = string.Empty; public string head { get; set; } = string.Empty; public string back { get; set; } = string.Empty; public string chest { get; set; } = string.Empty; } } namespace WeaponStatShower.Utils.Extensions { public static class CollectionExtensions { public static void ExpandToSize(this List list, int count, T defaultValue) { list.EnsureCapacity(count); while (list.Count < count) { list.Add(defaultValue); } } } } namespace WeaponStatShower.Patches { internal class DescriptionToggle : Patch { private const string PatchName = "DescriptionToggle"; private static DescriptionButtonHandler _button; public override string Name => "DescriptionToggle"; public static Patch Instance { get; private set; } public override void Initialize() { Instance = this; } public override void Execute() { PatchMethod("ShowWeaponSelectionPopup", PatchType.Postfix); PatchMethod("OnWeaponSlotItemSelected", PatchType.Postfix); } public static void CM_PlayerLobbyBar__ShowWeaponSelectionPopup__Postfix(CM_PlayerLobbyBar __instance) { _button = new DescriptionButtonHandler(__instance); if ((Object)(object)__instance.selectedWeaponSlotItem != (Object)null) { CM_PlayerLobbyBar__OnWeaponSlotItemSelected__Postfix(__instance.selectedWeaponSlotItem); } } public static void CM_PlayerLobbyBar__OnWeaponSlotItemSelected__Postfix(CM_InventorySlotItem slotItem) { if (_button != null) { _button.SetData(slotItem.m_gearID); } } } public abstract class Patch { protected internal Harmony? Harmony { get; set; } public virtual string Name { get; } public virtual bool Enabled => true; public virtual void Initialize() { } public abstract void Execute(); public void PatchConstructor(PatchType patchType, string? prefixMethodName = null, string? postfixMethodName = null) where TClass : class { PatchConstructor(null, patchType, prefixMethodName, postfixMethodName); } public void PatchConstructor(Type[]? parameters, PatchType patchType, string? prefixMethodName = null, string? postfixMethodName = null) where TClass : class { ConstructorInfo methodBase = AccessTools.Constructor(typeof(TClass), parameters, false); PatchMethod(methodBase, patchType, prefixMethodName, postfixMethodName); } public void PatchMethod(string methodName, PatchType patchType, Type[] generics = null, string prefixMethodName = null, string postfixMethodName = null) where TClass : class { PatchMethod(methodName, null, patchType, generics, prefixMethodName, postfixMethodName); } public void PatchMethod(string methodName, Type[] parameters, PatchType patchType, Type[] generics = null, string prefixMethodName = null, string postfixMethodName = null) where TClass : class { MethodInfo methodBase = AccessTools.Method(typeof(TClass), methodName, parameters, generics); PatchMethod(methodBase, patchType, prefixMethodName, postfixMethodName); } public void PatchMethod(MethodBase methodBase, PatchType patchType, string prefixMethodName = null, string postfixMethodName = null) where TClass : class { PatchMethod(typeof(TClass), methodBase, patchType, prefixMethodName, postfixMethodName); } public void PatchMethod(Type classType, string methodName, PatchType patchType, Type[] generics = null, string prefixMethodName = null, string postfixMethodName = null) { PatchMethod(classType, methodName, null, patchType, generics, prefixMethodName, postfixMethodName); } public void PatchMethod(Type classType, string methodName, Type[] parameters, PatchType patchType, Type[] generics = null, string prefixMethodName = null, string postfixMethodName = null) { MethodInfo methodBase = AccessTools.Method(classType, methodName, parameters, generics); PatchMethod(classType, methodBase, patchType, prefixMethodName, postfixMethodName); } public void PatchMethod(Type classType, MethodBase methodBase, PatchType patchType, string? prefixMethodName = null, string? postfixMethodName = null) { //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown //IL_0147: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown string text = classType.Name.Replace("`", "__"); string value = methodBase.ToString(); string text2 = (methodBase.IsConstructor ? "ctor" : methodBase.Name); MethodInfo methodInfo = null; MethodInfo methodInfo2 = null; if ((patchType & PatchType.Prefix) != 0) { try { methodInfo2 = AccessTools.Method(GetType(), prefixMethodName ?? (text + "__" + text2 + "__Prefix"), (Type[])null, (Type[])null); } catch (Exception value2) { WeaponStatShowerPlugin.LogFatal($"Failed to obtain the prefix patch method for {value}): {value2}"); } } if ((patchType & PatchType.Postfix) != 0) { try { methodInfo = AccessTools.Method(GetType(), postfixMethodName ?? (text + "__" + text2 + "__Postfix"), (Type[])null, (Type[])null); } catch (Exception value3) { WeaponStatShowerPlugin.LogFatal($"Failed to obtain the postfix patch method for {value}): {value3}"); } } try { if (methodInfo2 != null && methodInfo != null) { Harmony.Patch(methodBase, new HarmonyMethod(methodInfo2), new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if (methodInfo2 != null) { Harmony.Patch(methodBase, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if (methodInfo != null) { Harmony.Patch(methodBase, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception value4) { WeaponStatShowerPlugin.LogError($"Failed to patch method {value}: {value4}"); } } } [Flags] public enum PatchType : byte { Prefix = 1, Postfix = 2, Both = 3 } } namespace WeaponStatShower.Json { public static class JSON { private static readonly JsonSerializerOptions _setting; static JSON() { _setting = new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, IncludeFields = true, PropertyNameCaseInsensitive = true, WriteIndented = true, IgnoreReadOnlyProperties = true }; _setting.Converters.Add(new JsonStringEnumConverter()); if (PartialData.HasPData) { _setting.Converters.Add(PartialData.PDataIDConverter); } } public static T? Deserialize(string json) { return JsonSerializer.Deserialize(json, _setting); } public static T? Deserialize(ref Utf8JsonReader reader) { return JsonSerializer.Deserialize(ref reader, _setting); } public static bool TryDeserializeSafe(string json, [MaybeNullWhen(false)] out T value) { try { value = JsonSerializer.Deserialize(json, _setting); return value != null; } catch (JsonException ex) { WeaponStatShowerPlugin.LogError("Caught exception while reading json: " + ex.Message + "\n" + ex.StackTrace); value = default(T); return false; } } public static bool TryDeserialize(string json, [MaybeNullWhen(false)] out T value) { value = JsonSerializer.Deserialize(json, _setting); return value != null; } public static bool TryDeserialize(ref Utf8JsonReader reader, [MaybeNullWhen(false)] out T value) { value = JsonSerializer.Deserialize(ref reader, _setting); return value != null; } public static object? Deserialize(Type type, string json) { return JsonSerializer.Deserialize(json, type, _setting); } public static string Serialize(T value) { return JsonSerializer.Serialize(value, _setting); } public static void Serialize(Utf8JsonWriter writer, T value) { JsonSerializer.Serialize(writer, value, _setting); } public static void Serialize(Utf8JsonWriter writer, string name, T value) { writer.WritePropertyName(name); JsonSerializer.Serialize(writer, value, _setting); } } } namespace WeaponStatShower.Json.Converters.Utils { public class LocaleTextConverter : JsonConverter { public override bool HandleNull => true; public override LocaleText Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.String => new LocaleText(reader.GetString()), JsonTokenType.Number => new LocaleText(reader.GetUInt32()), JsonTokenType.Null => LocaleText.Empty, _ => throw new JsonException($"LocaleTextJson type: {reader.TokenType} is not implemented!"), }; } public override void Write(Utf8JsonWriter writer, LocaleText value, JsonSerializerOptions options) { if (value.ID != 0) { writer.WriteNumberValue(value.ID); } else { writer.WriteStringValue(value.RawText); } } } } namespace WeaponStatShower.ExtraDescription { public class DescriptionButtonHandler { private static readonly Vector2 Size = new Vector2(600f, 320f); private static readonly WeaponDescriptionBuilder DescriptionBuilder = new WeaponDescriptionBuilder(); private readonly GameObject _gameObject; private readonly CM_Item _item; private readonly TextMeshPro _text; private readonly RectTransform _rectTrans; private readonly CM_ScrollWindowInfoBox _infoBox; private readonly CM_PageLoadout _pageLoadout; private LocaleText[] _headers; private LocaleText[] _descriptions; private int _index; private GearIDRange? _lastRange; public event Action? OnDescriptionChanged; public DescriptionButtonHandler(CM_PlayerLobbyBar lobbyBar) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) _pageLoadout = ((Il2CppObjectBase)lobbyBar.m_parentPage).Cast(); _infoBox = lobbyBar.m_popupScrollWindow.InfoBox; _gameObject = Object.Instantiate(((Component)CM_PageLoadout.Current.m_copyLobbyIdButton).gameObject, ((Component)_infoBox).transform); _item = _gameObject.GetComponent(); _item.m_clickBlink = false; Transform child = ((Component)_item).transform.GetChild(0); _rectTrans = ((Component)child).GetComponent(); ((Component)child).gameObject.SetActive(false); _item.OnBtnPressCallback = null; _item.OnBtnPressCallback += Action.op_Implicit((Action)OnBtnPress); GameObject val = new GameObject("PageNumber"); val.transform.SetParent(_gameObject.transform, false); val.transform.localPosition = new Vector3(0f, -110f, 0f); val.layer = LayerManager.LAYER_UI; Color val2 = default(Color); ((Color)(ref val2))..ctor(1f, 1f, 1f, 0.2f); TextMeshPro val3 = ((Il2CppArrayBase)(object)_item.m_texts)[0]; _text = val.AddComponent(); ((TMP_Text)_text).font = ((TMP_Text)val3).font; ((TMP_Text)_text).fontSize = 48f; ((Graphic)_text).color = val2; ((TMP_Text)_text).alignment = (TextAlignmentOptions)1026; ((Graphic)_text).raycastTarget = ((Graphic)val3).raycastTarget; _text.sortingLayerID = val3.sortingLayerID; ((TMP_Text)_text).enableWordWrapping = false; ((TMP_Text)_text).isOrthographic = true; ((TMP_Text)_text).rectTransform.sizeDelta = new Vector2(100f, 50f); ((Il2CppArrayBase)(object)_item.m_texts)[0] = _text; Object.Destroy((Object)(object)val3); ((Component)_item).transform.localPosition = new Vector3(-300f, -50f - Size.y / 2f, -1f); _item.m_collider.size = Size; ((Il2CppArrayBase)(object)_item.m_textColorOut)[0] = val2; ((Il2CppArrayBase)(object)_item.m_textColorOver)[0] = Color.white; _item.m_alphaSpriteOnHover = true; } public void SetData(GearIDRange idRange) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Invalid comparison between Unknown and I4 if (_lastRange == idRange) { return; } _lastRange = idRange; uint compID = idRange.GetCompID((eGearComponent)2); GearCategoryDataBlock block = GameDataBlockBase.GetBlock(compID); ArchetypeDataBlock mappedArchetypeDataBlock = ArchetypeUtil.GetMappedArchetypeDataBlock(idRange, compID, block); bool flag = WeaponStatShowerPlugin.ShowStats; LocaleText localeText; bool flag2; ExtraDescriptionData data; if (mappedArchetypeDataBlock != null) { localeText = new LocaleText(mappedArchetypeDataBlock.Description); flag2 = DescriptionDataManager.TryGetArchData(((GameDataBlockBase)(object)mappedArchetypeDataBlock).persistentID, out data); } else { ItemDataBlock block2 = GameDataBlockBase.GetBlock(idRange.GetCompID((eGearComponent)3)); flag = flag && (int)block2.inventorySlot == 10; flag2 = DescriptionDataManager.TryGetGearData(compID, out data); localeText = new LocaleText(block.Description); } if (!flag && (!flag2 || data.Descriptions.Length == 0)) { _gameObject.SetActive(false); return; } List list = new List(); List list2 = new List(); int num = DescriptionDataManager.Current.GlobalSettings.DefaultDescriptionIndex; if (flag2) { if (data.DescriptionIndexOverride >= 0) { num = data.DescriptionIndexOverride; } list2.AddRange(data.Descriptions); list.AddRange(data.Headers); list.ExpandToSize(list2.Count, LocaleText.Empty); } LocaleText item = LocaleText.Empty; LocaleText item2 = new LocaleText((LocalizedText)localeText); if (flag) { var (localeText2, item3) = CreateGeneratedStats(idRange); switch (WeaponStatShowerPlugin.StatsLocation) { case WeaponStatShowerPlugin.StatsPosition.Last: list.Add(localeText2); list2.Add(item3); break; case WeaponStatShowerPlugin.StatsPosition.First: list.Insert(0, localeText2); list2.Insert(0, item3); if (num < list2.Count) { num++; } break; case WeaponStatShowerPlugin.StatsPosition.Combined: item = localeText2; item2 = new LocaleText(item3.ToString() + "\n" + item2); break; } } num = Math.Min(num, list2.Count); list2.Insert(num, item2); list.Insert(num, item); _descriptions = list2.ToArray(); _headers = list.ToArray(); LocaleText localeText3 = new LocaleText(idRange.PublicGearName); for (int i = 0; i < _headers.Length; i++) { if (_headers[i] == LocaleText.Empty) { _headers[i] = localeText3; } } _index = 0; if (num != 0 || WeaponStatShowerPlugin.StatsLocation == WeaponStatShowerPlugin.StatsPosition.Combined) { ((TMP_Text)_infoBox.m_infoMainTitleText).SetText((string)_headers[0], true); ((TMP_Text)_infoBox.m_infoDescriptionText).SetText((string)_descriptions[0], true); } if (_descriptions.Length == 1) { _gameObject.SetActive(false); return; } _gameObject.SetActive(true); ((TMP_Text)_text).SetText("<< 1 >>", true); } private void OnBtnPress(int _) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Vector2 val = RectTransformUtility.WorldToScreenPoint(CM_Camera.Current.Camera, ((CM_PageBase)_pageLoadout).CursorWorldPosition); Vector2 val2 = default(Vector2); RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTrans, val, CM_Camera.Current.Camera, ref val2); if (val2.x > 0f) { _index = (_index + 1) % _descriptions.Length; } else if (_index > 0) { _index--; } else { _index = _descriptions.Length - 1; } ((TMP_Text)_infoBox.m_infoMainTitleText).SetText((string)_headers[_index], true); ((TMP_Text)_infoBox.m_infoDescriptionText).SetText((string)_descriptions[_index], true); ((TMP_Text)_text).SetText($"<< {_index + 1} >>", true); } private static (LocaleText header, LocaleText desc) CreateGeneratedStats(GearIDRange idRange) { DescriptionBuilder.Inizialize(idRange, GameDataBlockBase.GetBlock(1u), WeaponStatShowerPlugin.ConfigLanguage); return (header: new LocaleText(DescriptionBuilder.FireRateFormatter(idRange.PublicGearName)), desc: new LocaleText(DescriptionBuilder.DescriptionFormatter())); } } public class DescriptionDataManager { public static readonly DescriptionDataManager Current = new DescriptionDataManager(); private const string GlobalFile = "Global.json"; private const string DataDir = "ExtraDescriptions"; private readonly Dictionary> _fileData = new Dictionary>(); private readonly Dictionary _archIdDataMap = new Dictionary(); private readonly Dictionary _gearIdDataMap = new Dictionary(); public GlobalData GlobalSettings { get; private set; } = new GlobalData(); private void GlobalFileChanged(LiveEditEventArgs e) { WeaponStatShowerPlugin.LogWarning("LiveEdit Global File Changed"); LiveEdit.TryReadFileContent(e.FullPath, (Action)delegate(string content) { ReadGlobalContent(content); }); } private void ReadGlobalContent(string content) { if (JSON.TryDeserializeSafe(content, out var value)) { GlobalSettings = value; } } private void DataFileChanged(LiveEditEventArgs e) { WeaponStatShowerPlugin.LogWarning("LiveEdit File Changed: " + e.FileName); LiveEdit.TryReadFileContent(e.FullPath, (Action)delegate(string content) { ReadDataContent(e.FullPath, content); }); } private void DataFileDeleted(LiveEditEventArgs e) { WeaponStatShowerPlugin.LogWarning("LiveEdit File Removed: " + e.FileName); if (!_fileData.Remove(e.FullPath, out List value)) { return; } foreach (ExtraDescriptionData item in value) { if (item.ArchetypeID != 0) { _archIdDataMap.Remove(item.ArchetypeID); } if (item.GearCategoryID != 0) { _gearIdDataMap.Remove(item.GearCategoryID); } } } private void DataFileCreated(LiveEditEventArgs e) { WeaponStatShowerPlugin.LogWarning("LiveEdit File Created: " + e.FileName); LiveEdit.TryReadFileContent(e.FullPath, (Action)delegate(string content) { ReadDataContent(e.FullPath, content); }); } private void ReadDataContent(string filepath, string content) { if (_fileData.TryGetValue(filepath, out List value)) { foreach (ExtraDescriptionData item in value) { if (item.ArchetypeID != 0) { _archIdDataMap.Remove(item.ArchetypeID); } if (item.GearCategoryID != 0) { _gearIdDataMap.Remove(item.GearCategoryID); } } value.Clear(); } else { _fileData.Add(filepath, value = new List()); } if (!JSON.TryDeserializeSafe>(content, out var value2)) { return; } value.EnsureCapacity(value2.Count); foreach (ExtraDescriptionData item2 in value2) { if (item2.ArchetypeID != 0 || item2.GearCategoryID != 0) { if (item2.ArchetypeID != 0 && !_archIdDataMap.TryAdd(item2.ArchetypeID, item2)) { WeaponStatShowerPlugin.LogWarning($"Duplicate archetype ID {item2.ArchetypeID} found. Name: {_archIdDataMap[item2.ArchetypeID].Name}, duplicate name: {item2.Name}"); } if (item2.GearCategoryID != 0 && !_gearIdDataMap.TryAdd(item2.GearCategoryID, item2)) { WeaponStatShowerPlugin.LogWarning($"Duplicate gear category ID {item2.GearCategoryID} found. Name: {_gearIdDataMap[item2.GearCategoryID].Name}, duplicate name: {item2.Name}"); } value.Add(item2); } } value.TrimExcess(); } public DescriptionDataManager() { //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown if (!MTFOWrapper.HasMTFO) { return; } string text = Path.Combine(MTFOWrapper.CustomPath, "WeaponStatShower"); string path = Path.Combine(text, "Global.json"); string text2 = Path.Combine(text, "ExtraDescriptions"); if (!Directory.Exists(text)) { WeaponStatShowerPlugin.LogInfo("No Weapon Stat Shower directory detected. Creating templates."); Directory.CreateDirectory(text2); StreamWriter streamWriter; using (streamWriter = File.CreateText(path)) { streamWriter.WriteLine(JSON.Serialize(new GlobalData())); } using (streamWriter = File.CreateText(Path.Combine(text2, "Template.json"))) { streamWriter.WriteLine(JSON.Serialize(ExtraDescriptionData.Template)); } } else if (!File.Exists(path)) { using StreamWriter streamWriter2 = File.CreateText(path); streamWriter2.WriteLine(JSON.Serialize(new GlobalData())); } ReadGlobalContent(File.ReadAllText(path)); foreach (string item in Directory.EnumerateFiles(text2, "*.json", SearchOption.AllDirectories)) { ReadDataContent(item, File.ReadAllText(item)); } LiveEdit.CreateListener(text, "Global.json", false).FileChanged += new LiveEditEventHandler(GlobalFileChanged); LiveEditListener obj = LiveEdit.CreateListener(text2, "*.json", true); obj.FileCreated += new LiveEditEventHandler(DataFileCreated); obj.FileChanged += new LiveEditEventHandler(DataFileChanged); obj.FileDeleted += new LiveEditEventHandler(DataFileDeleted); } internal void Init() { } public static bool TryGetArchData(uint archetypeID, [MaybeNullWhen(false)] out ExtraDescriptionData data) { return Current._archIdDataMap.TryGetValue(archetypeID, out data); } public static bool TryGetGearData(uint gearCategoryID, [MaybeNullWhen(false)] out ExtraDescriptionData data) { return Current._gearIdDataMap.TryGetValue(gearCategoryID, out data); } } } namespace WeaponStatShower.ExtraDescription.Data { public class ExtraDescriptionData { public static readonly ExtraDescriptionData[] Template = new ExtraDescriptionData[2] { new ExtraDescriptionData(), new ExtraDescriptionData { Headers = new LocaleText[2] { LocaleText.Empty, new LocaleText("Third Tab") }, Descriptions = new LocaleText[2] { new LocaleText("Custom tab for testing"), new LocaleText("Another custom tab under the third tab") }, Name = "Filled Example" } }; public uint ArchetypeID { get; set; } public uint GearCategoryID { get; set; } public LocaleText[] Headers { get; set; } = Array.Empty(); public LocaleText[] Descriptions { get; set; } = Array.Empty(); public int DescriptionIndexOverride { get; set; } = -1; public string Name { get; set; } = string.Empty; } public class GlobalData { public bool PreferHideStats { get; set; } public int DefaultDescriptionIndex { get; set; } } } namespace WeaponStatShower.Dependencies { internal static class MTFOWrapper { public const string PLUGIN_GUID = "com.dak.MTFO"; public static readonly bool HasMTFO; public static string GameDataPath => MTFOPathAPI.RundownPath; public static string CustomPath => MTFOPathAPI.CustomPath; public static bool HasCustomDatablocks { get { if (HasMTFO) { return UnsafeHasCustomDatablocks(); } return false; } } static MTFOWrapper() { HasMTFO = ((BaseChainloader)(object)IL2CPPChainloader.Instance).Plugins.ContainsKey("com.dak.MTFO"); } [MethodImpl(MethodImplOptions.NoInlining)] private static bool UnsafeHasCustomDatablocks() { return MTFOPathAPI.HasRundownPath; } public static void AddOnHotReload(Action action) { if (HasMTFO) { AddOnHotReload_Unsafe(action); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void AddOnHotReload_Unsafe(Action action) { MTFOHotReloadAPI.OnHotReload += action; } } internal static class PartialData { public const string PLUGIN_GUID = "MTFO.Extension.PartialBlocks"; public static readonly bool HasPData; public static JsonConverter PDataIDConverter; static PartialData() { HasPData = ((BaseChainloader)(object)IL2CPPChainloader.Instance).Plugins.ContainsKey("MTFO.Extension.PartialBlocks"); if (HasPData) { SetConverter(); } } public static bool TryGetGUID(string text, out uint guid) { if (!HasPData) { guid = 0u; return false; } return TryGetGUID_Internal(text, out guid); } [MethodImpl(MethodImplOptions.NoInlining)] private static bool TryGetGUID_Internal(string text, out uint guid) { return PersistentIDManager.TryGetId(text, ref guid); } [MethodImpl(MethodImplOptions.NoInlining)] private static void SetConverter() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown PDataIDConverter = (JsonConverter)new PersistentIDConverter(); } } }