using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.CharacterStats;
using PerfectRandom.Sulfur.Core.Items;
using PerfectRandom.Sulfur.Core.Stats;
using PerfectRandom.Sulfur.Core.UI.ItemDescription;
using PerfectRandom.Sulfur.Core.Weapons;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("PerfectOils")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PerfectOils")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("fca0f276-8db3-47a4-b077-acfc5373e719")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace PerfectOils
{
internal sealed class OilTraitService
{
internal sealed class OilDefinitionInfo
{
internal readonly EnchantmentDefinition Definition;
internal readonly bool HasMoreBulletDrop;
internal OilDefinitionInfo(EnchantmentDefinition definition)
{
Definition = definition;
HasMoreBulletDrop = NegativeTraitPolicy.HasMoreBulletDropTrait(((Object)(object)definition == (Object)null) ? null : definition.modifiersApplied);
}
}
private struct ModifierSignature
{
internal readonly ItemAttributes Attribute;
internal readonly StatModType ModType;
internal readonly float Value;
internal readonly NegativeOilTrait Traits;
internal ModifierSignature(ItemModifierContainer modifier, NegativeOilTrait traits)
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: 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)
Attribute = modifier.attribute;
ModType = modifier.modType;
Value = modifier.value;
Traits = traits;
}
internal bool Matches(ItemAttributes attribute, StatModifier modifier)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: 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)
return modifier != null && Attribute == attribute && ModType == modifier.Type && Math.Abs(Value - modifier.Value) <= 1E-06f;
}
}
private readonly ManualLogSource _log;
private readonly TraitConfiguration _settings;
private readonly Dictionary<uint, List<ModifierSignature>> _classifiedBySourceId = new Dictionary<uint, List<ModifierSignature>>();
private readonly Dictionary<ItemDefinition, OilDefinitionInfo> _oilInfoByItem = new Dictionary<ItemDefinition, OilDefinitionInfo>();
private readonly Dictionary<EnchantmentDefinition, OilDefinitionInfo> _oilInfoByDefinition = new Dictionary<EnchantmentDefinition, OilDefinitionInfo>();
private readonly Dictionary<EnchantmentDefinition, bool> _originalDurabilityFlags = new Dictionary<EnchantmentDefinition, bool>();
private bool _initialized;
private bool _loggedFirstRuntimeSuppression;
private bool _warnedUnavailableDatabase;
private bool _warnedEmptyDatabase;
private bool _warnedNoOilDefinitions;
internal bool IsInitialized => _initialized;
internal OilTraitService(ManualLogSource log, TraitConfiguration settings)
{
_log = log;
_settings = settings;
}
internal bool Initialize(AsyncAssetLoading assets, bool detailedLogging)
{
//IL_014b: Unknown result type (might be due to invalid IL or missing references)
//IL_01df: Unknown result type (might be due to invalid IL or missing references)
//IL_0281: Unknown result type (might be due to invalid IL or missing references)
//IL_0288: Invalid comparison between Unknown and I4
if (_initialized)
{
return true;
}
if ((Object)(object)assets == (Object)null || (Object)(object)assets.itemDatabase == (Object)null || (Object)(object)assets.enchantmentDatabase == (Object)null)
{
if (!_warnedUnavailableDatabase)
{
_log.LogWarning((object)"[PerfectOils] Asset loading completed, but an oil database was unavailable; initialization will be retried.");
_warnedUnavailableDatabase = true;
}
return false;
}
List<ItemDefinition> rawList = assets.itemDatabase.GetRawList();
if (rawList == null)
{
if (!_warnedUnavailableDatabase)
{
_log.LogWarning((object)"[PerfectOils] Item database returned no raw item list; initialization will be retried.");
_warnedUnavailableDatabase = true;
}
return false;
}
if (rawList.Count == 0)
{
if (!_warnedEmptyDatabase)
{
_log.LogWarning((object)"[PerfectOils] Item database is present but still empty; initialization will be retried.");
_warnedEmptyDatabase = true;
}
return false;
}
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
int num5 = 0;
int num6 = 0;
int num7 = 0;
int num8 = 0;
int num9 = 0;
for (int i = 0; i < rawList.Count; i++)
{
ItemDefinition val = rawList[i];
if (!IsOilItem(val))
{
continue;
}
num++;
EnchantmentDefinition val2;
try
{
val2 = assets.enchantmentDatabase[val.appliesEnchantment];
}
catch (Exception ex)
{
_log.LogWarning((object)("[PerfectOils] Could not resolve enchantment for oil '" + SafeOilName(val) + "': " + ex.Message));
continue;
}
if ((Object)(object)val2 == (Object)null)
{
continue;
}
if (!_oilInfoByDefinition.TryGetValue(val2, out var value))
{
value = new OilDefinitionInfo(val2);
_oilInfoByDefinition.Add(val2, value);
num2++;
uint key = GlobalId.op_Implicit(((EnchantmentId)(ref val2.id)).AsGlobalId());
List<ModifierSignature> list = new List<ModifierSignature>();
List<ItemModifierContainer> modifiersApplied = val2.modifiersApplied;
if (modifiersApplied != null)
{
for (int j = 0; j < modifiersApplied.Count; j++)
{
ItemModifierContainer val3 = modifiersApplied[j];
NegativeOilTrait negativeOilTrait = NegativeTraitPolicy.Classify(val3, value.HasMoreBulletDrop);
if (negativeOilTrait == NegativeOilTrait.None)
{
continue;
}
list.Add(new ModifierSignature(val3, negativeOilTrait));
num3++;
if ((negativeOilTrait & NegativeOilTrait.NegativeBulletSpeed) != NegativeOilTrait.None)
{
num5++;
}
if ((negativeOilTrait & NegativeOilTrait.NegativeDamage) != NegativeOilTrait.None)
{
if ((int)val3.modType == 100)
{
num6++;
}
else
{
num7++;
}
}
if ((negativeOilTrait & NegativeOilTrait.NegativeBulletSize) != NegativeOilTrait.None)
{
num8++;
}
if ((negativeOilTrait & NegativeOilTrait.NegativeRpm) != NegativeOilTrait.None)
{
num9++;
}
if (_settings.ShouldRemove(negativeOilTrait))
{
num4++;
}
if (detailedLogging)
{
_log.LogInfo((object)("[PerfectOils] Classified " + NegativeTraitPolicy.Describe(negativeOilTrait) + " from '" + SafeDefinitionName(val2, val) + "' [active=" + _settings.ShouldRemove(negativeOilTrait) + ", attribute=" + ((object)Unsafe.As<ItemAttributes, ItemAttributes>(ref val3.attribute)/*cast due to .constrained prefix*/).ToString() + ", type=" + ((object)Unsafe.As<StatModType, StatModType>(ref val3.modType)/*cast due to .constrained prefix*/).ToString() + ", value=" + val3.value + "]."));
}
}
}
if (list.Count > 0)
{
_classifiedBySourceId[key] = list;
}
_originalDurabilityFlags[val2] = val2.CostsDurability;
}
_oilInfoByItem[val] = value;
}
if (num == 0 || num2 == 0)
{
if (!_warnedNoOilDefinitions)
{
_log.LogWarning((object)("[PerfectOils] Scanned " + rawList.Count + " item definitions but found no runtime enchantment oils; initialization will be retried."));
_warnedNoOilDefinitions = true;
}
return false;
}
_initialized = true;
_log.LogInfo((object)("[PerfectOils] Scanned " + rawList.Count + " item definitions. Indexed " + num + " oil items across " + num2 + " unique enchantments; " + num3 + " known negative modifiers were classified, and " + num4 + " are currently enabled for suppression by config. Signed-value traits: bulletSpeed=" + num5 + ", damageFlat=" + num6 + ", damagePercent=" + num7 + ", bulletSize=" + num8 + ", rpm=" + num9 + "."));
return true;
}
internal unsafe bool ShouldSuppress(ItemAttributes attribute, StatModifier modifier)
{
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
if (!_initialized || modifier == null)
{
return false;
}
if (!_classifiedBySourceId.TryGetValue(modifier.SourceId, out var value))
{
return false;
}
for (int i = 0; i < value.Count; i++)
{
ModifierSignature modifierSignature = value[i];
if (modifierSignature.Matches(attribute, modifier) && _settings.ShouldRemove(modifierSignature.Traits))
{
if (!_loggedFirstRuntimeSuppression)
{
_loggedFirstRuntimeSuppression = true;
ManualLogSource log = _log;
string[] obj = new string[7]
{
"[PerfectOils] Runtime suppression is active; the first configured oil modifier was blocked [trait=",
NegativeTraitPolicy.Describe(modifierSignature.Traits),
", attribute=",
((object)(*(ItemAttributes*)(&attribute))/*cast due to .constrained prefix*/).ToString(),
", sourceId=",
null,
null
};
uint sourceId = modifier.SourceId;
obj[5] = sourceId.ToString();
obj[6] = "].";
log.LogInfo((object)string.Concat(obj));
}
return true;
}
}
return false;
}
internal bool TryGetOilInfo(InventoryItem item, out OilDefinitionInfo info)
{
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
info = null;
if (!_initialized || (Object)(object)item == (Object)null || (Object)(object)item.itemDefinition == (Object)null)
{
return false;
}
ItemDefinition itemDefinition = item.itemDefinition;
if (_oilInfoByItem.TryGetValue(itemDefinition, out info))
{
return true;
}
if (!((EnchantmentId)(ref itemDefinition.appliesEnchantment)).IsValid)
{
return false;
}
try
{
EnchantmentDefinition asset = AssetAccess.GetAsset(itemDefinition.appliesEnchantment);
return (Object)(object)asset != (Object)null && _oilInfoByDefinition.TryGetValue(asset, out info);
}
catch
{
info = null;
return false;
}
}
internal void RefreshDurabilityFlags(bool pluginEnabled)
{
bool flag = pluginEnabled && _settings.RemoveExtraDurabilityCost.Value;
foreach (KeyValuePair<EnchantmentDefinition, bool> originalDurabilityFlag in _originalDurabilityFlags)
{
if ((Object)(object)originalDurabilityFlag.Key != (Object)null)
{
originalDurabilityFlag.Key.CostsDurability = !flag && originalDurabilityFlag.Value;
}
}
}
internal void RestoreOriginalDefinitions()
{
foreach (KeyValuePair<EnchantmentDefinition, bool> originalDurabilityFlag in _originalDurabilityFlags)
{
if ((Object)(object)originalDurabilityFlag.Key != (Object)null)
{
originalDurabilityFlag.Key.CostsDurability = originalDurabilityFlag.Value;
}
}
_classifiedBySourceId.Clear();
_oilInfoByItem.Clear();
_oilInfoByDefinition.Clear();
_originalDurabilityFlags.Clear();
_initialized = false;
_loggedFirstRuntimeSuppression = false;
_warnedUnavailableDatabase = false;
_warnedEmptyDatabase = false;
_warnedNoOilDefinitions = false;
}
private static bool IsOilItem(ItemDefinition itemDefinition)
{
return (Object)(object)itemDefinition != (Object)null && itemDefinition.IsEnchantment && ((EnchantmentId)(ref itemDefinition.appliesEnchantment)).IsValid;
}
private static string SafeOilName(ItemDefinition oilItem)
{
if ((Object)(object)oilItem == (Object)null)
{
return "<null oil>";
}
if (!string.IsNullOrEmpty(oilItem.displayName))
{
return oilItem.displayName;
}
return ((object)Unsafe.As<ItemId, ItemId>(ref oilItem.id)/*cast due to .constrained prefix*/).ToString();
}
private static string SafeDefinitionName(EnchantmentDefinition definition, ItemDefinition oilItem)
{
if ((Object)(object)definition != (Object)null && !string.IsNullOrEmpty(definition.enchantmentName))
{
return definition.enchantmentName;
}
return SafeOilName(oilItem);
}
}
[Flags]
internal enum NegativeOilTrait
{
None = 0,
DisableAiming = 1,
MoreBulletDrop = 2,
MoreDrag = 4,
ExtraAmmoConsumeChance = 8,
DecreaseAccuracyWhenMoving = 0x10,
DecreaseMoveSpeed = 0x20,
DecreaseJumpPower = 0x40,
DecreaseLootChanceMultiplier = 0x80,
DisableMoneyDrops = 0x100,
DisableOrganDrops = 0x200,
NegativeBulletSpeed = 0x400,
NegativeDamage = 0x800,
NegativeBulletSize = 0x1000,
NegativeRpm = 0x2000,
ExtraDurabilityCost = 0x4000
}
internal static class NegativeTraitPolicy
{
private const float Epsilon = 1E-06f;
internal static bool HasMoreBulletDropTrait(IList<ItemModifierContainer> modifiers)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Invalid comparison between Unknown and I4
if (modifiers == null)
{
return false;
}
for (int i = 0; i < modifiers.Count; i++)
{
ItemModifierContainer val = modifiers[i];
if (val != null && (int)val.attribute == 102 && IncreasesAttribute(val))
{
return true;
}
}
return false;
}
internal static NegativeOilTrait Classify(ItemModifierContainer modifier, bool oilHasMoreBulletDrop)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Invalid comparison between Unknown and I4
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Invalid comparison between Unknown and I4
//IL_001f: 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_006b: Expected I4, but got Unknown
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Expected I4, but got Unknown
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Invalid comparison between Unknown and I4
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Invalid comparison between Unknown and I4
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Invalid comparison between Unknown and I4
if (modifier == null)
{
return NegativeOilTrait.None;
}
ItemAttributes attribute = modifier.attribute;
ItemAttributes val = attribute;
if ((int)val <= 52)
{
switch (val - 1)
{
default:
if ((int)val != 41)
{
if ((int)val != 52)
{
break;
}
return DecreasesAttribute(modifier) ? NegativeOilTrait.NegativeBulletSize : NegativeOilTrait.None;
}
return (oilHasMoreBulletDrop && IncreasesAttribute(modifier)) ? NegativeOilTrait.MoreBulletDrop : NegativeOilTrait.None;
case 7:
return IncreasesAttribute(modifier) ? NegativeOilTrait.DisableAiming : NegativeOilTrait.None;
case 2:
return IncreasesAttribute(modifier) ? NegativeOilTrait.ExtraAmmoConsumeChance : NegativeOilTrait.None;
case 0:
return DecreasesAttribute(modifier) ? NegativeOilTrait.DecreaseAccuracyWhenMoving : NegativeOilTrait.None;
case 16:
return DecreasesAttribute(modifier) ? NegativeOilTrait.DecreaseMoveSpeed : NegativeOilTrait.None;
case 14:
return DecreasesAttribute(modifier) ? NegativeOilTrait.DecreaseJumpPower : NegativeOilTrait.None;
case 15:
return DecreasesAttribute(modifier) ? NegativeOilTrait.DecreaseLootChanceMultiplier : NegativeOilTrait.None;
case 8:
return IncreasesAttribute(modifier) ? NegativeOilTrait.DisableMoneyDrops : NegativeOilTrait.None;
case 9:
return IncreasesAttribute(modifier) ? NegativeOilTrait.DisableOrganDrops : NegativeOilTrait.None;
case 5:
case 6:
return DecreasesAttribute(modifier) ? NegativeOilTrait.NegativeDamage : NegativeOilTrait.None;
case 1:
case 3:
case 4:
case 10:
case 11:
case 12:
case 13:
break;
}
}
else
{
if ((int)val == 56)
{
return DecreasesAttribute(modifier) ? NegativeOilTrait.NegativeRpm : NegativeOilTrait.None;
}
switch (val - 61)
{
default:
if ((int)val != 102)
{
break;
}
return IncreasesAttribute(modifier) ? NegativeOilTrait.MoreBulletDrop : NegativeOilTrait.None;
case 4:
return IncreasesAttribute(modifier) ? NegativeOilTrait.MoreDrag : NegativeOilTrait.None;
case 0:
if (!DecreasesAttribute(modifier))
{
return NegativeOilTrait.None;
}
return (NegativeOilTrait)(0x400 | (oilHasMoreBulletDrop ? 2 : 0));
case 2:
return IncreasesAttribute(modifier) ? NegativeOilTrait.ExtraDurabilityCost : NegativeOilTrait.None;
case 1:
case 3:
break;
}
}
return NegativeOilTrait.None;
}
internal static string Describe(NegativeOilTrait traits)
{
if (traits == NegativeOilTrait.None)
{
return "None";
}
List<string> list = new List<string>();
AddName(list, traits, NegativeOilTrait.DisableAiming, "Disable Aiming");
AddName(list, traits, NegativeOilTrait.MoreBulletDrop, "More Bullet Drop");
AddName(list, traits, NegativeOilTrait.MoreDrag, "More Drag");
AddName(list, traits, NegativeOilTrait.ExtraAmmoConsumeChance, "Extra Ammo Consume Chance");
AddName(list, traits, NegativeOilTrait.DecreaseAccuracyWhenMoving, "Decrease Accuracy When Moving");
AddName(list, traits, NegativeOilTrait.DecreaseMoveSpeed, "Decrease Move Speed");
AddName(list, traits, NegativeOilTrait.DecreaseJumpPower, "Decrease Jump Power");
AddName(list, traits, NegativeOilTrait.DecreaseLootChanceMultiplier, "Decrease Loot Chance Multiplier");
AddName(list, traits, NegativeOilTrait.DisableMoneyDrops, "Disable Money Drops");
AddName(list, traits, NegativeOilTrait.DisableOrganDrops, "Disable Organ Drops");
AddName(list, traits, NegativeOilTrait.NegativeBulletSpeed, "Negative Bullet Speed");
AddName(list, traits, NegativeOilTrait.NegativeDamage, "Negative Damage");
AddName(list, traits, NegativeOilTrait.NegativeBulletSize, "Negative Bullet Size");
AddName(list, traits, NegativeOilTrait.NegativeRpm, "Negative RPM");
AddName(list, traits, NegativeOilTrait.ExtraDurabilityCost, "Extra Oil Durability Cost");
return string.Join(" + ", list.ToArray());
}
private static bool IncreasesAttribute(ItemModifierContainer modifier)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
return IsSupportedModType(modifier.modType) && modifier.value > 1E-06f;
}
private static bool DecreasesAttribute(ItemModifierContainer modifier)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
return IsSupportedModType(modifier.modType) && modifier.value < -1E-06f;
}
private static bool IsSupportedModType(StatModType modType)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0004: Invalid comparison between Unknown and I4
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Invalid comparison between Unknown and I4
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Invalid comparison between Unknown and I4
return (int)modType == 100 || (int)modType == 200 || (int)modType == 300;
}
private static void AddName(List<string> names, NegativeOilTrait traits, NegativeOilTrait flag, string name)
{
if ((traits & flag) != NegativeOilTrait.None)
{
names.Add(name);
}
}
}
[BepInPlugin("com.ryuka.sulfur.perfectoils", "Perfect Oils", "1.3.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "com.ryuka.sulfur.perfectoils";
public const string PluginName = "Perfect Oils";
public const string PluginVersion = "1.3.0";
private const float InitializationRetryInterval = 1f;
private Harmony _harmony;
private AsyncAssetLoading _assetLoader;
private float _nextInitializationAttempt;
internal static Plugin Instance { get; private set; }
internal static ManualLogSource Log { get; private set; }
internal ConfigEntry<bool> Enabled { get; private set; }
internal ConfigEntry<bool> ShowRemovedTraitsWithStrikethrough { get; private set; }
internal ConfigEntry<bool> DetailedLogging { get; private set; }
internal TraitConfiguration TraitSettings { get; private set; }
internal OilTraitService OilTraits { get; private set; }
internal TooltipStrikeRenderer TooltipRenderer { get; private set; }
private void Awake()
{
//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
//IL_0107: Expected O, but got Unknown
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable Perfect Oils. Individual undesirable traits can be selected in the Traits section.");
ShowRemovedTraitsWithStrikethrough = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowRemovedTraitsWithStrikethrough", true, "Keep the original undesirable-trait text in oil tooltips and draw a strikethrough over lines currently disabled by this mod.");
DetailedLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DetailedLogging", false, "Log each classified oil modifier and tooltip line. Keep disabled during normal play.");
TraitSettings = new TraitConfiguration(((BaseUnityPlugin)this).Config);
OilTraits = new OilTraitService(((BaseUnityPlugin)this).Logger, TraitSettings);
TooltipRenderer = new TooltipStrikeRenderer(OilTraits, TraitSettings, ((BaseUnityPlugin)this).Logger);
Enabled.SettingChanged += OnDurabilitySettingChanged;
TraitSettings.RemoveExtraDurabilityCost.SettingChanged += OnDurabilitySettingChanged;
_harmony = new Harmony("com.ryuka.sulfur.perfectoils");
_harmony.PatchAll(typeof(Plugin).Assembly);
((BaseUnityPlugin)this).Logger.LogInfo((object)"[PerfectOils] Patches applied. Waiting for the SULFUR asset databases.");
}
private void Update()
{
if (OilTraits != null && !OilTraits.IsInitialized && !(Time.unscaledTime < _nextInitializationAttempt))
{
if ((Object)(object)_assetLoader == (Object)null)
{
_assetLoader = Object.FindFirstObjectByType<AsyncAssetLoading>();
}
if ((Object)(object)_assetLoader == (Object)null || !_assetLoader.loadingDone)
{
_nextInitializationAttempt = Time.unscaledTime + 1f;
}
else
{
TryInitializeOilTraits(_assetLoader);
}
}
}
internal void NotifyAssetsReady(AsyncAssetLoading assetLoader)
{
if (!((Object)(object)assetLoader == (Object)null))
{
_assetLoader = assetLoader;
TryInitializeOilTraits(assetLoader);
}
}
private void TryInitializeOilTraits(AsyncAssetLoading assetLoader)
{
if (OilTraits != null && !OilTraits.IsInitialized)
{
if (!OilTraits.Initialize(assetLoader, DetailedLogging.Value))
{
_nextInitializationAttempt = Time.unscaledTime + 1f;
}
else
{
OilTraits.RefreshDurabilityFlags(Enabled.Value);
}
}
}
private void OnDurabilitySettingChanged(object sender, EventArgs eventArgs)
{
if (OilTraits != null && OilTraits.IsInitialized)
{
OilTraits.RefreshDurabilityFlags(Enabled.Value);
}
}
private void OnDestroy()
{
Enabled.SettingChanged -= OnDurabilitySettingChanged;
if (TraitSettings != null)
{
TraitSettings.RemoveExtraDurabilityCost.SettingChanged -= OnDurabilitySettingChanged;
}
if (OilTraits != null)
{
OilTraits.RestoreOriginalDefinitions();
}
if (_harmony != null)
{
_harmony.UnpatchSelf();
}
if (Instance == this)
{
Instance = null;
Log = null;
}
}
}
internal sealed class TooltipStrikeRenderer
{
private enum LineKind
{
Attribute,
Description
}
private sealed class LineSpec
{
internal readonly LineKind Kind;
internal readonly string Key;
internal readonly string Value;
internal readonly bool IsRemoved;
internal LineSpec(LineKind kind, string key, string value, bool isRemoved)
{
Kind = kind;
Key = key ?? string.Empty;
Value = value ?? string.Empty;
IsRemoved = isRemoved;
}
}
private sealed class RenderedLine
{
internal readonly LineKind Kind;
internal readonly int SiblingIndex;
internal readonly string Key;
internal readonly string Value;
internal readonly TMP_Text[] TextComponents;
internal RenderedLine(LineKind kind, int siblingIndex, string key, string value, TMP_Text[] textComponents)
{
Kind = kind;
SiblingIndex = siblingIndex;
Key = key ?? string.Empty;
Value = value ?? string.Empty;
TextComponents = textComponents;
}
}
private static class LocalizationBridge
{
private static readonly MethodInfo TryGetTranslationMethod = FindTryGetTranslation();
internal static string GetTranslationOrFallback(string term, string fallback)
{
if (TryGetTranslationMethod != null)
{
try
{
object[] array = new object[9] { term, null, true, 0, true, false, null, null, true };
bool flag = (bool)TryGetTranslationMethod.Invoke(null, array);
string text = array[1] as string;
if (flag && !string.IsNullOrEmpty(text))
{
return text;
}
}
catch
{
}
}
return fallback ?? string.Empty;
}
private static MethodInfo FindTryGetTranslation()
{
Type type = AccessTools.TypeByName("I2.Loc.LocalizationManager");
if (type == null)
{
return null;
}
MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
foreach (MethodInfo methodInfo in methods)
{
if (!(methodInfo.Name != "TryGetTranslation"))
{
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length == 9 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(string).MakeByRefType())
{
return methodInfo;
}
}
}
return null;
}
}
private static readonly FieldInfo AttributesInUseField = AccessTools.Field(typeof(ItemDescription), "attributesInUse");
private static readonly FieldInfo DescriptionTextInUseField = AccessTools.Field(typeof(ItemDescription), "descriptionTextInUse");
private static readonly FieldInfo KeyTextField = AccessTools.Field(typeof(ItemDescriptionAttribute), "keyText");
private static readonly FieldInfo ValueTextField = AccessTools.Field(typeof(ItemDescriptionAttribute), "valueText");
private static readonly FieldInfo ModifierTextField = AccessTools.Field(typeof(ItemDescriptionAttribute), "modifierText");
private static readonly MethodInfo FormatModifierValueMethod = AccessTools.Method(typeof(ItemDescription), "GetValueAsStringDependingOnModType", new Type[2]
{
typeof(ItemModifierContainer),
typeof(ItemAttribute)
}, (Type[])null);
private readonly OilTraitService _oilTraits;
private readonly TraitConfiguration _settings;
private readonly ManualLogSource _log;
private bool _loggedFirstStrikethrough;
internal TooltipStrikeRenderer(OilTraitService oilTraits, TraitConfiguration settings, ManualLogSource log)
{
_oilTraits = oilTraits;
_settings = settings;
_log = log;
}
internal void Apply(ItemDescription description, InventoryItem item, bool detailedLogging)
{
if ((Object)(object)description == (Object)null || (Object)(object)item == (Object)null || !_oilTraits.TryGetOilInfo(item, out var info) || info == null || (Object)(object)info.Definition == (Object)null || info.Definition.modifiersApplied == null)
{
return;
}
List<LineSpec> list = BuildLineSpecs(description, info);
if (list.Count == 0)
{
return;
}
List<RenderedLine> list2 = CollectRenderedLines(description);
if (list2.Count == 0)
{
return;
}
List<int> list3 = FindBestOrderedMatch(list2, list);
if (list3 == null)
{
list3 = FindRemovedLinesFallback(list2, list);
if (detailedLogging)
{
_log.LogWarning((object)("[PerfectOils] Could not match the complete oil modifier block for '" + SafeItemName(item) + "'; used exact removed-line fallback matching."));
}
}
if (list3 == null)
{
return;
}
int num = 0;
for (int i = 0; i < list.Count && i < list3.Count; i++)
{
if (list[i].IsRemoved && list3[i] >= 0)
{
ApplyStrikethrough(list2[list3[i]]);
num++;
}
}
if (num > 0 && !_loggedFirstStrikethrough)
{
_loggedFirstStrikethrough = true;
_log.LogInfo((object)"[PerfectOils] Tooltip integration is active; removed oil traits are being shown with strikethrough.");
}
if (detailedLogging && num > 0)
{
_log.LogInfo((object)("[PerfectOils] Marked " + num + " removed trait line(s) in the tooltip for '" + SafeItemName(item) + "'."));
}
}
private List<LineSpec> BuildLineSpecs(ItemDescription description, OilTraitService.OilDefinitionInfo info)
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Invalid comparison between Unknown and I4
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
List<LineSpec> list = new List<LineSpec>();
List<ItemModifierContainer> modifiersApplied = info.Definition.modifiersApplied;
for (int i = 0; i < modifiersApplied.Count; i++)
{
ItemModifierContainer val = modifiersApplied[i];
if (val == null || (int)val.attribute == 0)
{
continue;
}
ItemAttribute asset;
try
{
asset = AssetAccess.GetAsset(val.attribute);
}
catch
{
continue;
}
if ((Object)(object)asset == (Object)null || !asset.showInItemDescription)
{
continue;
}
NegativeOilTrait traits = NegativeTraitPolicy.Classify(val, info.HasMoreBulletDrop);
bool isRemoved = _settings.ShouldRemove(traits);
if (asset.simplifiedModAmount)
{
bool flag = val.value > 0f;
string term = string.Format(flag ? "ItemAttributes/{0}_simplifiedIncrease" : "ItemAttributes/{0}_simplifiedDecrease", val.attribute);
string fallback = (flag ? asset.simplifiedIncreaseString : asset.simplifiedDecreaseString);
string translationOrFallback = LocalizationBridge.GetTranslationOrFallback(term, fallback);
if (!string.IsNullOrEmpty(translationOrFallback))
{
list.Add(new LineSpec(LineKind.Description, translationOrFallback, string.Empty, isRemoved));
}
continue;
}
string term2 = $"ItemAttributes/{val.attribute}_itemDescription";
string translationOrFallback2 = LocalizationBridge.GetTranslationOrFallback(term2, asset.itemDescriptionName);
if (asset.isBooleanAttribute)
{
if (!string.IsNullOrEmpty(translationOrFallback2))
{
list.Add(new LineSpec(LineKind.Description, translationOrFallback2, string.Empty, isRemoved));
}
}
else
{
string value = FormatModifierValue(description, val, asset);
string key = translationOrFallback2 + (string.IsNullOrEmpty(value) ? string.Empty : ":");
list.Add(new LineSpec(LineKind.Attribute, key, value, isRemoved));
}
}
return list;
}
private static string FormatModifierValue(ItemDescription description, ItemModifierContainer modifier, ItemAttribute attribute)
{
if (FormatModifierValueMethod == null)
{
return modifier.GetValueString();
}
try
{
return ((string)FormatModifierValueMethod.Invoke(description, new object[2] { modifier, attribute })) ?? string.Empty;
}
catch
{
return modifier.GetValueString() ?? string.Empty;
}
}
private static List<RenderedLine> CollectRenderedLines(ItemDescription description)
{
List<RenderedLine> list = new List<RenderedLine>();
List<ItemDescriptionAttribute> list2 = ((AttributesInUseField == null) ? null : (AttributesInUseField.GetValue(description) as List<ItemDescriptionAttribute>));
if (list2 != null)
{
for (int i = 0; i < list2.Count; i++)
{
ItemDescriptionAttribute val = list2[i];
if (!((Object)(object)val == (Object)null))
{
TMP_Text textField = GetTextField(KeyTextField, val);
TMP_Text textField2 = GetTextField(ValueTextField, val);
TMP_Text textField3 = GetTextField(ModifierTextField, val);
list.Add(new RenderedLine(LineKind.Attribute, ((Component)val).transform.GetSiblingIndex(), GetComparableText(textField), GetComparableText(textField2), (TMP_Text[])(object)new TMP_Text[3] { textField, textField2, textField3 }));
}
}
}
List<ItemDescriptionText> list3 = ((DescriptionTextInUseField == null) ? null : (DescriptionTextInUseField.GetValue(description) as List<ItemDescriptionText>));
if (list3 != null)
{
for (int j = 0; j < list3.Count; j++)
{
ItemDescriptionText val2 = list3[j];
if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.textComp == (Object)null))
{
int siblingIndex = ((Component)val2).transform.GetSiblingIndex();
string comparableText = GetComparableText((TMP_Text)(object)val2.textComp);
string empty = string.Empty;
TMP_Text[] textComponents = (TMP_Text[])(object)new TextMeshProUGUI[1] { val2.textComp };
list.Add(new RenderedLine(LineKind.Description, siblingIndex, comparableText, empty, textComponents));
}
}
}
list.Sort(delegate(RenderedLine left, RenderedLine right)
{
int siblingIndex2 = left.SiblingIndex;
return siblingIndex2.CompareTo(right.SiblingIndex);
});
return list;
}
private static TMP_Text GetTextField(FieldInfo field, object instance)
{
return (TMP_Text)((field == null) ? null : /*isinst with value type is only supported in some contexts*/);
}
private static string GetComparableText(TMP_Text text)
{
return ((Object)(object)text == (Object)null) ? string.Empty : RemoveOwnStrikeTags(text.text ?? string.Empty);
}
private static List<int> FindBestOrderedMatch(List<RenderedLine> lines, List<LineSpec> specs)
{
List<int> result = null;
int num = int.MaxValue;
for (int i = 0; i < lines.Count; i++)
{
if (!Matches(lines[i], specs[0]))
{
continue;
}
List<int> list = new List<int>(specs.Count) { i };
int num2 = i + 1;
bool flag = true;
for (int j = 1; j < specs.Count; j++)
{
int num3 = -1;
for (int k = num2; k < lines.Count; k++)
{
if (Matches(lines[k], specs[j]))
{
num3 = k;
break;
}
}
if (num3 < 0)
{
flag = false;
break;
}
list.Add(num3);
num2 = num3 + 1;
}
if (flag)
{
int num4 = list[list.Count - 1] - list[0];
if (num4 < num)
{
result = list;
num = num4;
}
}
}
return result;
}
private static List<int> FindRemovedLinesFallback(List<RenderedLine> lines, List<LineSpec> specs)
{
List<int> list = new List<int>(specs.Count);
HashSet<int> hashSet = new HashSet<int>();
bool flag = false;
for (int i = 0; i < specs.Count; i++)
{
if (!specs[i].IsRemoved)
{
list.Add(-1);
continue;
}
int item = -1;
for (int j = 0; j < lines.Count; j++)
{
if (!hashSet.Contains(j) && Matches(lines[j], specs[i]))
{
item = j;
hashSet.Add(j);
flag = true;
break;
}
}
list.Add(item);
}
return flag ? list : null;
}
private static bool Matches(RenderedLine line, LineSpec spec)
{
return line.Kind == spec.Kind && string.Equals(line.Key, spec.Key, StringComparison.Ordinal) && string.Equals(line.Value, spec.Value, StringComparison.Ordinal);
}
private static void ApplyStrikethrough(RenderedLine line)
{
for (int i = 0; i < line.TextComponents.Length; i++)
{
TMP_Text val = line.TextComponents[i];
if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.text))
{
string text = RemoveOwnStrikeTags(val.text);
val.text = "<s>" + text + "</s>";
}
}
}
private static string RemoveOwnStrikeTags(string text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
if (text.StartsWith("<s>", StringComparison.Ordinal) && text.EndsWith("</s>", StringComparison.Ordinal))
{
return text.Substring("<s>".Length, text.Length - "<s>".Length - "</s>".Length);
}
return text;
}
private static string SafeItemName(InventoryItem item)
{
if ((Object)(object)item == (Object)null || (Object)(object)item.itemDefinition == (Object)null)
{
return "<unknown oil>";
}
return string.IsNullOrEmpty(item.itemDefinition.displayName) ? ((object)item.itemDefinition).ToString() : item.itemDefinition.displayName;
}
}
internal sealed class TraitConfiguration
{
internal ConfigEntry<bool> RemoveDisableAiming { get; private set; }
internal ConfigEntry<bool> RemoveMoreBulletDrop { get; private set; }
internal ConfigEntry<bool> RemoveMoreDrag { get; private set; }
internal ConfigEntry<bool> RemoveExtraAmmoConsumeChance { get; private set; }
internal ConfigEntry<bool> RemoveDecreaseAccuracyWhenMoving { get; private set; }
internal ConfigEntry<bool> RemoveDecreaseMoveSpeed { get; private set; }
internal ConfigEntry<bool> RemoveDecreaseJumpPower { get; private set; }
internal ConfigEntry<bool> RemoveDecreaseLootChanceMultiplier { get; private set; }
internal ConfigEntry<bool> RemoveDisableMoneyDrops { get; private set; }
internal ConfigEntry<bool> RemoveDisableOrganDrops { get; private set; }
internal ConfigEntry<bool> RemoveNegativeBulletSpeed { get; private set; }
internal ConfigEntry<bool> RemoveNegativeDamage { get; private set; }
internal ConfigEntry<bool> RemoveNegativeBulletSize { get; private set; }
internal ConfigEntry<bool> RemoveNegativeRpm { get; private set; }
internal ConfigEntry<bool> RemoveExtraDurabilityCost { get; private set; }
internal TraitConfiguration(ConfigFile config)
{
RemoveDisableAiming = config.Bind<bool>("Traits", "RemoveDisableAiming", true, "Remove oil modifiers that disable aiming down sights.");
RemoveMoreBulletDrop = config.Bind<bool>("Traits", "RemoveMoreBulletDrop", false, "Remove increased projectile gravity and its same-oil speed/mass companion penalties.");
RemoveMoreDrag = config.Bind<bool>("Traits", "RemoveMoreDrag", false, "Remove oil modifiers that increase projectile drag.");
RemoveExtraAmmoConsumeChance = config.Bind<bool>("Traits", "RemoveExtraAmmoConsumeChance", true, "Remove oil modifiers that add a chance to consume extra ammunition.");
RemoveDecreaseAccuracyWhenMoving = config.Bind<bool>("Traits", "RemoveDecreaseAccuracyWhenMoving", true, "Remove oil modifiers that reduce accuracy while moving.");
RemoveDecreaseMoveSpeed = config.Bind<bool>("Traits", "RemoveDecreaseMoveSpeed", true, "Remove oil modifiers that reduce movement speed while holding the weapon.");
RemoveDecreaseJumpPower = config.Bind<bool>("Traits", "RemoveDecreaseJumpPower", true, "Remove oil modifiers that reduce jump power while holding the weapon.");
RemoveDecreaseLootChanceMultiplier = config.Bind<bool>("Traits", "RemoveDecreaseLootChanceMultiplier", true, "Remove oil modifiers that reduce the loot chance multiplier.");
RemoveDisableMoneyDrops = config.Bind<bool>("Traits", "RemoveDisableMoneyDrops", true, "Remove oil modifiers that disable Sulf/money drops.");
RemoveDisableOrganDrops = config.Bind<bool>("Traits", "RemoveDisableOrganDrops", true, "Remove oil modifiers that disable organ drops.");
RemoveNegativeBulletSpeed = config.Bind<bool>("Traits", "RemoveNegativeBulletSpeed", false, "Remove ProjectileTimeScale modifiers only when they reduce bullet speed. Positive bullet-speed modifiers remain active.");
RemoveNegativeDamage = config.Bind<bool>("Traits", "RemoveNegativeDamage", false, "Remove flat or percentage damage modifiers only when their signed value is negative. Positive damage modifiers remain active.");
RemoveNegativeBulletSize = config.Bind<bool>("Traits", "RemoveNegativeBulletSize", false, "Remove ProjectileScale modifiers only when they reduce bullet size. Positive bullet-size modifiers remain active.");
RemoveNegativeRpm = config.Bind<bool>("Traits", "RemoveNegativeRpm", false, "Remove RPM modifiers only when their signed value is negative. Positive fire-rate modifiers remain active.");
RemoveExtraDurabilityCost = config.Bind<bool>("General", "RemoveExtraDurabilityCost", false, "Remove oil-specific extra durability consumption. This setting is independent from the other undesirable traits.");
}
internal bool ShouldRemove(NegativeOilTrait traits)
{
return IsEnabled(traits, NegativeOilTrait.DisableAiming, RemoveDisableAiming) || IsEnabled(traits, NegativeOilTrait.MoreBulletDrop, RemoveMoreBulletDrop) || IsEnabled(traits, NegativeOilTrait.MoreDrag, RemoveMoreDrag) || IsEnabled(traits, NegativeOilTrait.ExtraAmmoConsumeChance, RemoveExtraAmmoConsumeChance) || IsEnabled(traits, NegativeOilTrait.DecreaseAccuracyWhenMoving, RemoveDecreaseAccuracyWhenMoving) || IsEnabled(traits, NegativeOilTrait.DecreaseMoveSpeed, RemoveDecreaseMoveSpeed) || IsEnabled(traits, NegativeOilTrait.DecreaseJumpPower, RemoveDecreaseJumpPower) || IsEnabled(traits, NegativeOilTrait.DecreaseLootChanceMultiplier, RemoveDecreaseLootChanceMultiplier) || IsEnabled(traits, NegativeOilTrait.DisableMoneyDrops, RemoveDisableMoneyDrops) || IsEnabled(traits, NegativeOilTrait.DisableOrganDrops, RemoveDisableOrganDrops) || IsEnabled(traits, NegativeOilTrait.NegativeBulletSpeed, RemoveNegativeBulletSpeed) || IsEnabled(traits, NegativeOilTrait.NegativeDamage, RemoveNegativeDamage) || IsEnabled(traits, NegativeOilTrait.NegativeBulletSize, RemoveNegativeBulletSize) || IsEnabled(traits, NegativeOilTrait.NegativeRpm, RemoveNegativeRpm) || IsEnabled(traits, NegativeOilTrait.ExtraDurabilityCost, RemoveExtraDurabilityCost);
}
private static bool IsEnabled(NegativeOilTrait traits, NegativeOilTrait flag, ConfigEntry<bool> setting)
{
return (traits & flag) != NegativeOilTrait.None && setting.Value;
}
}
}
namespace PerfectOils.Patches
{
[HarmonyPatch(typeof(AsyncAssetLoading), "set_loadingDone")]
internal static class AssetLoadingPatches
{
[HarmonyPostfix]
private static void AfterLoadingDoneSet(AsyncAssetLoading __instance, bool value)
{
Plugin instance = Plugin.Instance;
if (value && !((Object)(object)instance == (Object)null))
{
instance.NotifyAssetsReady(__instance);
}
}
}
[HarmonyPatch]
internal static class ItemDescriptionPatches
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(ItemDescription), "Setup", new Type[1] { typeof(InventoryItem) }, (Type[])null);
}
[HarmonyPostfix]
private static void AfterSetup(ItemDescription __instance, InventoryItem __0)
{
Plugin instance = Plugin.Instance;
if (!((Object)(object)instance == (Object)null) && instance.Enabled.Value && instance.ShowRemovedTraitsWithStrikethrough.Value && instance.TooltipRenderer != null)
{
instance.TooltipRenderer.Apply(__instance, __0, instance.DetailedLogging.Value);
}
}
}
[HarmonyPatch]
internal static class ItemStatsPatches
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(ItemStats), "AddModifier", new Type[2]
{
typeof(ItemAttributes),
typeof(StatModifier)
}, (Type[])null);
}
[HarmonyPrefix]
private static bool BeforeAddModifier(ItemAttributes __0, StatModifier __1)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
Plugin instance = Plugin.Instance;
if ((Object)(object)instance == (Object)null || !instance.Enabled.Value || instance.OilTraits == null)
{
return true;
}
return !instance.OilTraits.ShouldSuppress(__0, __1);
}
}
[HarmonyPatch(typeof(Weapon), "SyncEnchantments")]
internal static class WeaponPatches
{
[HarmonyPrefix]
private static void ResetCachedAimingState(ref bool ___aimingDisabled)
{
___aimingDisabled = false;
}
}
}