Decompiled source of Ice Sniper Staff v1.0.0

plugins/SniperStaff.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
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("SniperStaff")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SniperStaff")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SniperStaff;

[BepInPlugin("dots.icesniperstaff", "Ice Sniper Staff", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class SniperStaffPlugin : BaseUnityPlugin
{
	public const string PluginGUID = "dots.icesniperstaff";

	public const string WizardryGUID = "Therzie.Wizardry";

	public const string PluginName = "Ice Sniper Staff";

	public const string PluginVersion = "1.0.0";

	internal static ManualLogSource Log;

	private readonly Harmony _harmony = new Harmony("dots.icesniperstaff");

	internal const string NewPrefab = "RuneStaffSniper";

	internal const string VanillaReference = "CrossbowArbalest";

	private const string BundleResource = "SniperStaff.Assets.bundles.sniperstaff";

	private const string BundleName = "sniperstaff";

	internal const string CustomProjectile = "RuneStaffSniper_projectile";

	private const string ReloadClipName = "reload_sfx";

	private const string IconName = "icon_icestaff";

	internal static GameObject StaffProjectile;

	internal static AudioClip ReloadClip;

	private static bool _wizardry;

	private static readonly string[] ProjectileCandidates = new string[3] { "RuneStaffSniper_projectile", "staff_iceshard_projectile", "staff_fireball_projectile" };

	private static readonly string[] ProjectileBaseCandidates = new string[3] { "staff_iceshard_projectile", "DvergerStaffIce_projectile", "staff_fireball_projectile" };

	internal static ConfigEntry<float> Damage;

	internal static ConfigEntry<float> PierceDamage;

	internal static ConfigEntry<float> FrostDamage;

	internal static ConfigEntry<float> PierceDamagePerLevel;

	internal static ConfigEntry<float> FrostDamagePerLevel;

	internal static ConfigEntry<float> Velocity;

	internal static ConfigEntry<float> Accuracy;

	internal static ConfigEntry<float> EitrCost;

	internal static ConfigEntry<float> ReloadTime;

	internal static ConfigEntry<float> ReloadVolume;

	internal static ConfigEntry<bool> SilenceReloadAnim;

	internal static ConfigEntry<bool> FlatShot;

	internal static ConfigEntry<bool> NoExplosion;

	internal static ConfigEntry<float> ProjectileScale;

	internal static ConfigEntry<float> NW_PierceDamage;

	internal static ConfigEntry<float> NW_FrostDamage;

	internal static ConfigEntry<float> NW_PierceDamagePerLevel;

	internal static ConfigEntry<float> NW_FrostDamagePerLevel;

	internal static ConfigEntry<float> NW_Velocity;

	internal static ConfigEntry<float> NW_EitrCost;

	internal static ConfigEntry<float> NW_ReloadTime;

	private static readonly Dictionary<string, Color> MaterialColors = new Dictionary<string, Color>
	{
		{
			"Mat_Wood",
			new Color(0.45f, 0.3f, 0.16f, 1f)
		},
		{
			"Mat_Metal",
			new Color(0.55f, 0.57f, 0.6f, 1f)
		}
	};

	private static readonly HashSet<string> PreserveMaterials = new HashSet<string> { "Mat_Crystal" };

	private AssetBundle _bundle;

	private static bool _projectileReady;

	private static bool _soundsApplied;

	private static readonly string[] FireSfxCandidates = new string[3] { "fx_iceshard_launch", "sfx_dverger_ice_projectile_start", "sfx_icestaff_start" };

	private static float EffPierce => _wizardry ? PierceDamage.Value : NW_PierceDamage.Value;

	private static float EffFrost => _wizardry ? FrostDamage.Value : NW_FrostDamage.Value;

	private static float EffPiercePerLvl => _wizardry ? PierceDamagePerLevel.Value : NW_PierceDamagePerLevel.Value;

	private static float EffFrostPerLvl => _wizardry ? FrostDamagePerLevel.Value : NW_FrostDamagePerLevel.Value;

	private static float EffEitr => _wizardry ? EitrCost.Value : NW_EitrCost.Value;

	private static float EffReload => _wizardry ? ReloadTime.Value : NW_ReloadTime.Value;

	internal static float EffVelocity => _wizardry ? Velocity.Value : NW_Velocity.Value;

	private void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		PrefabManager.OnVanillaPrefabsAvailable += RegisterStaff;
		_harmony.PatchAll();
		Log.LogInfo((object)"Ice Sniper Staff v1.0.0 loaded.");
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
		if ((Object)(object)_bundle != (Object)null)
		{
			_bundle.Unload(false);
		}
	}

	private void BindConfig()
	{
		Damage = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "DamageMultiplier", 1f, "Damage multiplier applied at fire time (1 = use raw values below).");
		PierceDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "PierceDamage", 70f, "Pierce damage per shot (quality 1).");
		FrostDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "FrostDamage", 170f, "Frost damage per shot (quality 1).");
		PierceDamagePerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "PierceDamagePerLevel", 10f, "Extra pierce damage gained per upgrade level.");
		FrostDamagePerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "FrostDamagePerLevel", 15f, "Extra frost damage gained per upgrade level.");
		Velocity = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "Velocity", 240f, "Projectile speed at fire time (sniper = fast).");
		Accuracy = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "Accuracy", 0f, "Aim spread in degrees.");
		EitrCost = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "EitrCost", 40f, "Eitr cost per shot.");
		ReloadTime = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "ReloadTime", 2f, "Crossbow-style reload/draw time in seconds.");
		ReloadVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "ReloadVolume", 1f, "Reload sound volume (0 = silent, 1 = full).");
		SilenceReloadAnim = ((BaseUnityPlugin)this).Config.Bind<bool>("Sniper", "SilenceReloadAnim", true, "Clear the reload animation to silence the vanilla crossbow reload sound. May also remove the visible reload animation.");
		FlatShot = ((BaseUnityPlugin)this).Config.Bind<bool>("Sniper", "FlatShot", true, "Remove projectile gravity for a flat line.");
		NoExplosion = ((BaseUnityPlugin)this).Config.Bind<bool>("Sniper", "NoExplosion", true, "Strip the AoE so it's a clean single-target dart.");
		ProjectileScale = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper", "ProjectileScale", 1.4f, "Visual size multiplier for the projectile (1 = vanilla).");
		NW_PierceDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "PierceDamage", 95f, "Pierce damage per shot (quality 1) when Wizardry is absent.");
		NW_FrostDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "FrostDamage", 230f, "Frost damage per shot (quality 1) when Wizardry is absent.");
		NW_PierceDamagePerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "PierceDamagePerLevel", 14f, "Extra pierce per upgrade level when Wizardry is absent.");
		NW_FrostDamagePerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "FrostDamagePerLevel", 20f, "Extra frost per upgrade level when Wizardry is absent.");
		NW_Velocity = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "Velocity", 300f, "Projectile speed when Wizardry is absent.");
		NW_EitrCost = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "EitrCost", 40f, "Eitr cost per shot when Wizardry is absent.");
		NW_ReloadTime = ((BaseUnityPlugin)this).Config.Bind<float>("Sniper.NoWizardry", "ReloadTime", 1.7f, "Reload time when Wizardry is absent (lower = faster).");
	}

	private void RegisterStaff()
	{
		//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ea: Expected O, but got Unknown
		try
		{
			_wizardry = Chainloader.PluginInfos.ContainsKey("Therzie.Wizardry");
			Log.LogInfo((object)("Wizardry detected: " + _wizardry + " (mode: " + (_wizardry ? "Wizardry baseline" : "standalone / Galdr Table, buffed") + ")."));
			_bundle = AssetUtils.LoadAssetBundleFromResources("sniperstaff", Assembly.GetExecutingAssembly());
			if ((Object)(object)_bundle == (Object)null)
			{
				Log.LogError((object)"AssetBundle 'sniperstaff' not found as embedded resource 'SniperStaff.Assets.bundles.sniperstaff'.");
				return;
			}
			GameObject val = _bundle.LoadAsset<GameObject>("RuneStaffSniper");
			if ((Object)(object)val == (Object)null)
			{
				Log.LogError((object)"Prefab 'RuneStaffSniper' not found inside bundle 'sniperstaff'.");
				return;
			}
			ReloadClip = _bundle.LoadAsset<AudioClip>("reload_sfx");
			if ((Object)(object)ReloadClip == (Object)null)
			{
				Log.LogError((object)"Reload clip 'reload_sfx' NOT found in bundle 'sniperstaff'. Check the AssetBundle assignment in Unity and the clip name.");
			}
			else
			{
				Log.LogInfo((object)("Reload clip 'reload_sfx' loaded, length=" + ReloadClip.length + "s."));
			}
			Shader val2 = Shader.Find("Standard");
			Log.LogDebug((object)("Game Shader.Find(\"Standard\") = " + (((Object)(object)val2 == (Object)null) ? "NULL" : (((Object)val2).name + " supported=" + val2.isSupported))));
			GameObject prefab = PrefabManager.Instance.GetPrefab("CrossbowArbalest");
			if ((Object)(object)prefab == (Object)null)
			{
				Log.LogError((object)"Vanilla reference 'CrossbowArbalest' not found! Check the exact prefab name in LogOutput.log.");
			}
			MeshRenderer val3 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponentInChildren<MeshRenderer>(true) : null);
			Shader val4 = (((Object)(object)val3 != (Object)null && (Object)(object)((Renderer)val3).sharedMaterial != (Object)null) ? ((Renderer)val3).sharedMaterial.shader : null);
			Log.LogDebug((object)("Vanilla mesh shader = " + (((Object)(object)val4 == (Object)null) ? "NULL" : (((Object)val4).name + " supported=" + val4.isSupported))));
			FixMaterials(val, val2, val4);
			ItemConfig val5 = BuildRecipe();
			ConfigureWeapon(val, prefab);
			Sprite val6 = _bundle.LoadAsset<Sprite>("icon_icestaff");
			if ((Object)(object)val6 != (Object)null)
			{
				Log.LogInfo((object)"Custom icon 'icon_icestaff' loaded from bundle.");
			}
			else
			{
				Log.LogWarning((object)"Custom icon 'icon_icestaff' NOT found in bundle; rendering a fallback. Check the sprite's Texture Type = Sprite and its AssetBundle assignment in Unity.");
				val6 = RenderManager.Instance.Render(val, RenderManager.IsometricRotation);
			}
			if ((Object)(object)val6 != (Object)null)
			{
				val5.Icon = val6;
			}
			else
			{
				Log.LogWarning((object)"No icon available (bundle sprite missing and render returned null).");
			}
			ItemManager.Instance.AddItem(new CustomItem(val, true, val5));
			AddLocalization();
			Log.LogInfo((object)"Registered RuneStaffSniper from bundle.");
		}
		catch (Exception ex)
		{
			Log.LogError((object)("RegisterStaff failed: " + ex));
		}
		finally
		{
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterStaff;
		}
	}

	private static ItemConfig BuildRecipe()
	{
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Expected O, but got Unknown
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_014c: Expected O, but got Unknown
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_015f: 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_0170: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Expected O, but got Unknown
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_018c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Expected O, but got Unknown
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Expected O, but got Unknown
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Expected O, but got Unknown
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Expected O, but got Unknown
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Expected O, but got Unknown
		//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ee: Expected O, but got Unknown
		string craftingStation;
		int minStationLevel;
		RequirementConfig[] requirements;
		if (_wizardry)
		{
			craftingStation = "WizardTable_TW";
			minStationLevel = 4;
			requirements = (RequirementConfig[])(object)new RequirementConfig[4]
			{
				new RequirementConfig
				{
					Item = "EnchantedWoodMountain_TW",
					Amount = 20,
					AmountPerLevel = 0,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "Staffcore_Mountain_TW",
					Amount = 3,
					AmountPerLevel = 0,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "Silver",
					Amount = 5,
					AmountPerLevel = 0,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "ArcaneScroll_Mountain_TW",
					Amount = 0,
					AmountPerLevel = 2,
					Recover = false
				}
			};
			Log.LogDebug((object)"Recipe: Wizardry detected -> WizardTable_TW lvl 4, scroll upgrades.");
		}
		else
		{
			craftingStation = "piece_magetable";
			minStationLevel = 1;
			requirements = (RequirementConfig[])(object)new RequirementConfig[4]
			{
				new RequirementConfig
				{
					Item = "YggdrasilWood",
					Amount = 10,
					AmountPerLevel = 5,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "FreezeGland",
					Amount = 6,
					AmountPerLevel = 3,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "Eitr",
					Amount = 12,
					AmountPerLevel = 5,
					Recover = true
				},
				new RequirementConfig
				{
					Item = "Iron",
					Amount = 4,
					AmountPerLevel = 2,
					Recover = true
				}
			};
			Log.LogWarning((object)"Recipe: Wizardry NOT installed -> Galdr Table (piece_magetable) lvl 1, frost-staff recipe.");
		}
		return new ItemConfig
		{
			Name = "$item_runestaff_sniper",
			Description = "$item_runestaff_sniper_desc",
			CraftingStation = craftingStation,
			MinStationLevel = minStationLevel,
			Requirements = requirements
		};
	}

	private void ConfigureWeapon(GameObject prefab, GameObject vanilla)
	{
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01da: 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_0266: Unknown result type (might be due to invalid IL or missing references)
		//IL_0268: Unknown result type (might be due to invalid IL or missing references)
		ItemDrop component = prefab.GetComponent<ItemDrop>();
		if ((Object)(object)component == (Object)null || (Object)(object)vanilla == (Object)null)
		{
			Log.LogWarning((object)("Could not copy weapon data: drop=" + ((Object)(object)component != (Object)null) + " vanilla=" + ((Object)(object)vanilla != (Object)null)));
			return;
		}
		SharedData shared = vanilla.GetComponent<ItemDrop>().m_itemData.m_shared;
		SharedData shared2 = component.m_itemData.m_shared;
		shared2.m_itemType = shared.m_itemType;
		shared2.m_animationState = shared.m_animationState;
		shared2.m_attack = shared.m_attack.Clone();
		shared2.m_secondaryAttack = shared.m_secondaryAttack.Clone();
		shared2.m_skillType = (SkillType)9;
		shared2.m_attack.m_reloadTime = EffReload;
		shared2.m_attack.m_reloadStaminaDrain = 0f;
		shared2.m_attack.m_reloadEitrDrain = 0f;
		Log.LogDebug((object)("Reload animation (original) = '" + shared2.m_attack.m_reloadAnimation + "'"));
		if (SilenceReloadAnim.Value)
		{
			shared2.m_attack.m_reloadAnimation = "";
			Log.LogDebug((object)"SilenceReloadAnim: cleared m_reloadAnimation.");
		}
		DamageTypes damages = shared.m_damages;
		damages.m_damage = 0f;
		damages.m_blunt = 0f;
		damages.m_slash = 0f;
		damages.m_pierce = EffPierce;
		damages.m_chop = 0f;
		damages.m_pickaxe = 0f;
		damages.m_fire = 0f;
		damages.m_frost = EffFrost;
		damages.m_lightning = 0f;
		damages.m_poison = 0f;
		damages.m_spirit = 0f;
		shared2.m_damages = damages;
		DamageTypes damagesPerLevel = shared.m_damagesPerLevel;
		damagesPerLevel.m_damage = 0f;
		damagesPerLevel.m_blunt = 0f;
		damagesPerLevel.m_slash = 0f;
		damagesPerLevel.m_pierce = EffPiercePerLvl;
		damagesPerLevel.m_chop = 0f;
		damagesPerLevel.m_pickaxe = 0f;
		damagesPerLevel.m_fire = 0f;
		damagesPerLevel.m_frost = EffFrostPerLvl;
		damagesPerLevel.m_lightning = 0f;
		damagesPerLevel.m_poison = 0f;
		damagesPerLevel.m_spirit = 0f;
		shared2.m_damagesPerLevel = damagesPerLevel;
		shared2.m_attackForce = shared.m_attackForce;
		shared2.m_useDurability = shared.m_useDurability;
		shared2.m_maxDurability = shared.m_maxDurability;
		shared2.m_destroyBroken = shared.m_destroyBroken;
		shared2.m_equipDuration = shared.m_equipDuration;
		shared2.m_aiAttackRange = shared.m_aiAttackRange;
		shared2.m_ammoType = "";
		shared2.m_attack.m_attackEitr = EffEitr;
		shared2.m_attack.m_attackStamina = 0f;
		shared2.m_attack.m_drawStaminaDrain = 0f;
		shared2.m_name = "$item_runestaff_sniper";
		shared2.m_description = "$item_runestaff_sniper_desc";
		shared2.m_maxQuality = 4;
		shared2.m_attack.m_projectiles = 1;
		shared2.m_attack.m_projectileBursts = 1;
		shared2.m_attack.m_burstInterval = 0f;
		Log.LogDebug((object)("Setup -> attackAnim='" + shared2.m_attack.m_attackAnimation + "', skill=" + ((object)Unsafe.As<SkillType, SkillType>(ref shared2.m_skillType)/*cast due to .constrained prefix*/).ToString() + ", animState=" + ((object)Unsafe.As<AnimationState, AnimationState>(ref shared2.m_animationState)/*cast due to .constrained prefix*/).ToString() + ", ammoType='" + shared2.m_ammoType + "', attackEitr=" + shared2.m_attack.m_attackEitr + ", reloadTime=" + shared2.m_attack.m_reloadTime + ")."));
		Log.LogDebug((object)("VERIFY stats (" + (_wizardry ? "Wizardry" : "NoWizardry") + ") -> pierce=" + shared2.m_damages.m_pierce + " frost=" + shared2.m_damages.m_frost + " | perLevel pierce=" + shared2.m_damagesPerLevel.m_pierce + " frost=" + shared2.m_damagesPerLevel.m_frost + " | velocity=" + EffVelocity + " | maxQuality=" + shared2.m_maxQuality + " (multiplier at fire=" + Damage.Value + ")."));
	}

	internal static void EnsureProjectileReady()
	{
		if (_projectileReady || (Object)(object)ZNetScene.instance == (Object)null)
		{
			return;
		}
		BuildCustomProjectile();
		StaffProjectile = ResolveProjectile();
		if ((Object)(object)StaffProjectile != (Object)null && ((Object)StaffProjectile).name == "RuneStaffSniper_projectile")
		{
			_projectileReady = true;
			Log.LogDebug((object)"EnsureProjectileReady: custom projectile is live.");
		}
		else
		{
			Log.LogWarning((object)("EnsureProjectileReady: still using fallback '" + (((Object)(object)StaffProjectile != (Object)null) ? ((Object)StaffProjectile).name : "NULL") + "'."));
		}
		if ((Object)(object)StaffProjectile != (Object)null)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab("RuneStaffSniper");
			ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val != (Object)null)
			{
				val.m_itemData.m_shared.m_attack.m_attackProjectile = StaffProjectile;
				Log.LogDebug((object)("EnsureProjectileReady: shared m_attackProjectile set -> " + ((Object)StaffProjectile).name + "."));
			}
		}
		ApplyFireSound();
	}

	private static void ApplyFireSound()
	{
		if (_soundsApplied)
		{
			return;
		}
		GameObject prefab = PrefabManager.Instance.GetPrefab("RuneStaffSniper");
		if ((Object)(object)prefab == (Object)null)
		{
			return;
		}
		ItemDrop component = prefab.GetComponent<ItemDrop>();
		if (!((Object)(object)component == (Object)null))
		{
			SharedData shared = component.m_itemData.m_shared;
			GameObject val = FirstExisting(FireSfxCandidates);
			if ((Object)(object)val != (Object)null)
			{
				SetEffect(shared.m_attack.m_startEffect, val);
				SetEffect(shared.m_attack.m_triggerEffect, val);
				Log.LogDebug((object)("ApplyFireSound: fire sound -> " + ((Object)val).name));
			}
			else
			{
				Log.LogWarning((object)"ApplyFireSound: no fire sfx candidate found; keeping vanilla.");
			}
			_soundsApplied = true;
		}
	}

	private static GameObject FirstExisting(string[] names)
	{
		foreach (string text in names)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab(text);
			if ((Object)(object)prefab != (Object)null)
			{
				return prefab;
			}
		}
		return null;
	}

	private static void SetEffect(EffectList list, GameObject prefab)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		if (list != null && !((Object)(object)prefab == (Object)null))
		{
			list.m_effectPrefabs = (EffectData[])(object)new EffectData[1]
			{
				new EffectData
				{
					m_prefab = prefab,
					m_enabled = true,
					m_variant = -1
				}
			};
		}
	}

	private static void BuildCustomProjectile()
	{
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0171: Expected O, but got Unknown
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		//IL_017a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)PrefabManager.Instance.GetPrefab("RuneStaffSniper_projectile") != (Object)null)
		{
			return;
		}
		GameObject val = null;
		string text = null;
		string[] projectileBaseCandidates = ProjectileBaseCandidates;
		foreach (string text2 in projectileBaseCandidates)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab(text2);
			if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Projectile>() != (Object)null)
			{
				val = prefab;
				text = text2;
				break;
			}
			Log.LogDebug((object)("BuildCustomProjectile: base candidate '" + text2 + "' -> " + (((Object)(object)prefab == (Object)null) ? "not found" : "no Projectile component")));
		}
		if ((Object)(object)val == (Object)null)
		{
			Log.LogError((object)"BuildCustomProjectile: no usable base projectile found; staff will use fallback.");
			return;
		}
		Log.LogDebug((object)("BuildCustomProjectile: cloning base '" + text + "'."));
		GameObject val2 = PrefabManager.Instance.CreateClonedPrefab("RuneStaffSniper_projectile", val);
		if ((Object)(object)val2 == (Object)null)
		{
			Log.LogError((object)"BuildCustomProjectile: CreateClonedPrefab returned null.");
			return;
		}
		float num = Mathf.Max(0.1f, ProjectileScale.Value);
		if (Mathf.Abs(num - 1f) > 0.001f)
		{
			foreach (Transform item in val2.transform)
			{
				Transform val3 = item;
				val3.localScale *= num;
			}
		}
		Projectile component = val2.GetComponent<Projectile>();
		if ((Object)(object)component != (Object)null)
		{
			ApplyProjectilePhysics(component);
			if (NoExplosion.Value)
			{
				StripSpawnOnHit(component);
			}
		}
		else
		{
			Log.LogWarning((object)"BuildCustomProjectile: clone has no Projectile component?!");
		}
		Log.LogDebug((object)("BuildCustomProjectile: created 'RuneStaffSniper_projectile' from base, gravity=" + (((Object)(object)component != (Object)null) ? component.m_gravity.ToString() : "n/a") + ", noExplosion=" + NoExplosion.Value));
	}

	internal static void ApplyProjectilePhysics(Projectile proj)
	{
		if (!((Object)(object)proj == (Object)null))
		{
			if (FlatShot.Value)
			{
				proj.m_gravity = 0f;
			}
			proj.m_rayRadius = Mathf.Max(proj.m_rayRadius, 0.2f);
		}
	}

	private static void StripSpawnOnHit(Projectile proj)
	{
		FieldInfo[] fields = typeof(Projectile).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		FieldInfo[] array = fields;
		foreach (FieldInfo fieldInfo in array)
		{
			if (fieldInfo.Name.IndexOf("spawnOnHit", StringComparison.OrdinalIgnoreCase) < 0)
			{
				continue;
			}
			try
			{
				if (typeof(GameObject).IsAssignableFrom(fieldInfo.FieldType))
				{
					fieldInfo.SetValue(proj, null);
				}
				else if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType) && fieldInfo.GetValue(proj) is IList list)
				{
					list.Clear();
				}
				Log.LogDebug((object)("StripSpawnOnHit: cleared field '" + fieldInfo.Name + "'."));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("StripSpawnOnHit: could not clear '" + fieldInfo.Name + "': " + ex.Message));
			}
		}
	}

	private static GameObject ResolveProjectile()
	{
		string[] projectileCandidates = ProjectileCandidates;
		foreach (string text in projectileCandidates)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab(text);
			if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Projectile>() != (Object)null)
			{
				Log.LogDebug((object)("ResolveProjectile: using '" + text + "'."));
				return prefab;
			}
		}
		return null;
	}

	private static void FixMaterials(GameObject root, Shader gameStandard, Shader vanillaShader)
	{
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		Shader val = null;
		val = (((Object)(object)gameStandard != (Object)null && gameStandard.isSupported) ? gameStandard : ((!((Object)(object)vanillaShader != (Object)null) || !vanillaShader.isSupported) ? Shader.Find("Custom/Creature") : vanillaShader));
		if ((Object)(object)val == (Object)null)
		{
			Log.LogError((object)"FixMaterials: no usable shader found; staff will render pink/white.");
			return;
		}
		Log.LogDebug((object)("FixMaterials: using shader '" + ((Object)val).name + "'."));
		int num = 0;
		Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val2 in componentsInChildren)
		{
			Material[] sharedMaterials = val2.sharedMaterials;
			foreach (Material val3 in sharedMaterials)
			{
				if ((Object)(object)val3 == (Object)null)
				{
					continue;
				}
				val3.shader = val;
				if (PreserveMaterials.Contains(((Object)val3).name))
				{
					num++;
					continue;
				}
				if (!MaterialColors.TryGetValue(((Object)val3).name, out var value))
				{
					((Color)(ref value))..ctor(0.7f, 0.7f, 0.7f, 1f);
				}
				if (val3.HasProperty("_Color"))
				{
					val3.SetColor("_Color", value);
				}
				if (val3.HasProperty("_MainColor"))
				{
					val3.SetColor("_MainColor", value);
				}
				if (val3.HasProperty("_Glossiness"))
				{
					val3.SetFloat("_Glossiness", 0.2f);
				}
				if (val3.HasProperty("_Smoothness"))
				{
					val3.SetFloat("_Smoothness", 0.2f);
				}
				if (val3.HasProperty("_Metallic"))
				{
					val3.SetFloat("_Metallic", 0f);
				}
				num++;
			}
		}
		Log.LogDebug((object)("FixMaterials: repaired " + num + " materials (preserved color on: " + string.Join(", ", PreserveMaterials) + ")."));
	}

	private void AddLocalization()
	{
		CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
		string text = "English";
		localization.AddTranslation(ref text, new Dictionary<string, string>
		{
			{ "item_runestaff_sniper", "Ice Sniper Staff" },
			{ "item_runestaff_sniper_desc", "A focusing rune mounted on a crossbow-stock. Draws like an arbalest, looses a single ice bolt in a dead-straight line. Slow to load, devastating on a hit." }
		});
	}

	internal static bool IsSniperWeapon(ItemData weapon)
	{
		return weapon != null && (Object)(object)weapon.m_dropPrefab != (Object)null && ((Object)weapon.m_dropPrefab).name == "RuneStaffSniper";
	}
}
[HarmonyPatch(typeof(DungeonDB), "Start")]
internal static class ProjectileWarmupPatch
{
	private static void Postfix()
	{
		try
		{
			SniperStaffPlugin.EnsureProjectileReady();
		}
		catch (Exception ex)
		{
			SniperStaffPlugin.Log.LogWarning((object)("Warmup failed (will retry on fire): " + ex.Message));
		}
	}
}
[HarmonyPatch(typeof(Attack), "FireProjectileBurst")]
internal static class AttackFirePatch
{
	private static void Prefix(Attack __instance)
	{
		if (__instance == null || __instance.m_weapon == null || !SniperStaffPlugin.IsSniperWeapon(__instance.m_weapon))
		{
			return;
		}
		Attack attack = __instance.m_weapon.m_shared.m_attack;
		if ((Object)(object)SniperStaffPlugin.StaffProjectile == (Object)null || ((Object)SniperStaffPlugin.StaffProjectile).name != "RuneStaffSniper_projectile")
		{
			SniperStaffPlugin.EnsureProjectileReady();
		}
		if ((Object)(object)SniperStaffPlugin.StaffProjectile != (Object)null)
		{
			__instance.m_attackProjectile = SniperStaffPlugin.StaffProjectile;
			attack.m_attackProjectile = SniperStaffPlugin.StaffProjectile;
			float effVelocity = SniperStaffPlugin.EffVelocity;
			float value = SniperStaffPlugin.Accuracy.Value;
			__instance.m_projectileVel = effVelocity;
			__instance.m_projectileVelMin = effVelocity;
			__instance.m_projectileAccuracy = value;
			__instance.m_projectileAccuracyMin = value;
			__instance.m_damageMultiplier = SniperStaffPlugin.Damage.Value;
			if (SniperStaffPlugin.FlatShot.Value && (Object)(object)__instance.m_attackProjectile != (Object)null)
			{
				Projectile component = __instance.m_attackProjectile.GetComponent<Projectile>();
				SniperStaffPlugin.ApplyProjectilePhysics(component);
			}
		}
		else
		{
			SniperStaffPlugin.Log.LogWarning((object)"AttackFirePatch: projectile still null at fire; shot skipped.");
		}
	}
}
[HarmonyPatch(typeof(Attack), "HaveAmmo")]
internal static class AmmoBypassPatch
{
	private static void Postfix(Attack __instance, ref bool __result)
	{
		if (__instance != null && __instance.m_weapon != null && SniperStaffPlugin.IsSniperWeapon(__instance.m_weapon))
		{
			__result = true;
		}
	}
}
[HarmonyPatch(typeof(Player), "UpdateActionQueue")]
internal static class ReloadSoundPatch
{
	private static bool _wasReloading;

	private static void Postfix(Player __instance)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Invalid comparison between Unknown and I4
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)SniperStaffPlugin.ReloadClip == (Object)null))
		{
			ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon();
			bool flag = SniperStaffPlugin.IsSniperWeapon(currentWeapon);
			string text = default(string);
			float num = default(float);
			MinorActionData val = default(MinorActionData);
			__instance.GetActionProgress(ref text, ref num, ref val);
			bool flag2 = flag && val != null && (int)val.m_type == 2;
			if (_wasReloading && !flag2 && flag && ((Humanoid)__instance).IsWeaponLoaded())
			{
				PlayReloadSound(((Component)__instance).transform.position);
			}
			_wasReloading = flag2;
		}
	}

	private static void PlayReloadSound(Vector3 pos)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Expected O, but got Unknown
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("SniperReloadSfx");
		val.transform.position = pos;
		AudioSource val2 = val.AddComponent<AudioSource>();
		val2.clip = SniperStaffPlugin.ReloadClip;
		val2.volume = Mathf.Clamp01(SniperStaffPlugin.ReloadVolume.Value);
		val2.spatialBlend = 1f;
		val2.minDistance = 3f;
		val2.maxDistance = 30f;
		val2.Play();
		Object.Destroy((Object)(object)val, SniperStaffPlugin.ReloadClip.length + 0.1f);
	}
}