Decompiled source of REPOShock v0.0.9

BepInEx/plugins/mxpuffin-REPOShock/REPOShock.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PiShockClassLibrary.Models;
using PiShockClassLibrary.Services;
using PiShockLibrary.Enums;
using PiShockLibrary.Models;
using PiShockLibrary.Utilities;
using Sirenix.Serialization.Utilities;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("Autodesk.Fbx")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("FbxBuildTestAssets")]
[assembly: IgnoresAccessChecksTo("Klattersynth")]
[assembly: IgnoresAccessChecksTo("Photon3Unity3D")]
[assembly: IgnoresAccessChecksTo("PhotonChat")]
[assembly: IgnoresAccessChecksTo("PhotonRealtime")]
[assembly: IgnoresAccessChecksTo("PhotonUnityNetworking")]
[assembly: IgnoresAccessChecksTo("PhotonUnityNetworking.Utilities")]
[assembly: IgnoresAccessChecksTo("PhotonVoice.API")]
[assembly: IgnoresAccessChecksTo("PhotonVoice")]
[assembly: IgnoresAccessChecksTo("PhotonVoice.PUN")]
[assembly: IgnoresAccessChecksTo("SingularityGroup.HotReload.Runtime")]
[assembly: IgnoresAccessChecksTo("SingularityGroup.HotReload.Runtime.Public")]
[assembly: IgnoresAccessChecksTo("Sirenix.OdinInspector.Attributes")]
[assembly: IgnoresAccessChecksTo("Sirenix.Serialization.Config")]
[assembly: IgnoresAccessChecksTo("Sirenix.Serialization")]
[assembly: IgnoresAccessChecksTo("Sirenix.Utilities")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Formats.Fbx.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.Postprocessing.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Antlr3.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Core")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.Flow")]
[assembly: IgnoresAccessChecksTo("Unity.VisualScripting.State")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: IgnoresAccessChecksTo("websocket-sharp")]
[assembly: AssemblyCompany("mxpuffin")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+43894436dbd289d7963110c7041a713eee6ece98")]
[assembly: AssemblyProduct("REPOShock")]
[assembly: AssemblyTitle("REPOShock")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace REPOShock.src
{
	internal class AsyncPiShockRequestQueue : IDisposable
	{
		private ConcurrentQueue<Func<Task>> _taskQueue;

		private SemaphoreSlim _signal;

		private CancellationTokenSource _cts;

		private Task _worker;

		private readonly object _lock = new object();

		private bool _disposed;

		public AsyncPiShockRequestQueue()
		{
			Start();
		}

		public void Enqueue<T>(Func<T, Task> task, T args)
		{
			Func<T, Task> task2 = task;
			T args2 = args;
			if (_disposed)
			{
				throw new ObjectDisposedException("AsyncPiShockRequestQueue");
			}
			_taskQueue.Enqueue(() => task2(args2));
			_signal.Release();
		}

		public void Start()
		{
			_taskQueue = new ConcurrentQueue<Func<Task>>();
			_signal = new SemaphoreSlim(0);
			_cts = new CancellationTokenSource();
			_worker = Task.Run((Func<Task?>)ProcessQueueAsync);
		}

		private async Task ProcessQueueAsync()
		{
			while (!_cts.Token.IsCancellationRequested)
			{
				await _signal.WaitAsync(_cts.Token);
				if (_taskQueue.TryDequeue(out Func<Task> result))
				{
					try
					{
						await result();
					}
					catch (Exception ex)
					{
						REPOShock.Logger.LogError((object)ex.ToString());
					}
				}
			}
		}

		public async Task StopAsync()
		{
			if (_disposed)
			{
				return;
			}
			_cts.Cancel();
			_signal.Release();
			try
			{
				await _worker;
			}
			catch (Exception ex)
			{
				REPOShock.Logger.LogError((object)ex.ToString());
			}
		}

		public async Task ResetAsync()
		{
			lock (_lock)
			{
				if (!_worker.IsCompleted)
				{
					StopAsync().Wait();
				}
				Start();
			}
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				StopAsync().Wait();
				_cts.Dispose();
				_signal.Dispose();
				_disposed = true;
			}
		}
	}
	internal class ConfigHandler
	{
		internal static ConfigHandler Instance { get; private set; }

		public static ConfigEntry<int>? ConfigMaxIntensity { get; private set; }

		public static ConfigEntry<int>? ConfigDeathIntensity { get; private set; }

		public static ConfigEntry<int>? ConfigDeathDuration { get; private set; }

		public static ConfigEntry<float>? ConfigDamageInteravl { get; private set; }

		public static ConfigEntry<bool>? ConfigEnableBreakShock { get; private set; }

		public static ConfigEntry<bool>? ConfigBreakIgnoreEnemy { get; private set; }

		public static ConfigEntry<bool>? ConfigMapValueToShock { get; private set; }

		public static ConfigEntry<float>? ConfigBreakValueDmgMult { get; private set; }

		public static ConfigEntry<int>? ConfigBreakShockWindow { get; private set; }

		public static ConfigEntry<float>? ThrownOffensiveGracePeriod { get; private set; }

		public static ConfigEntry<float>? HitOffensiveGracePeriod { get; private set; }

		public static void InitConfig()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Expected O, but got Unknown
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Expected O, but got Unknown
			ConfigMaxIntensity = REPOShock.CFG.Bind<int>("Settings", "Max_Intensity", 100, new ConfigDescription("The maximum amount of an intensity a shock will ever be.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			ConfigDamageInteravl = REPOShock.CFG.Bind<float>("Settings", "Damage_Interval", 1f, new ConfigDescription("The intensity for each damage dealt to you. Shock will always be rounded up.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
			ConfigDeathIntensity = REPOShock.CFG.Bind<int>("Settings", "Death_Intensity", 50, new ConfigDescription("The intensity when you die. (1-100)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			ConfigDeathDuration = REPOShock.CFG.Bind<int>("Settings", "Max_Duration", 3, new ConfigDescription("The duration when you die. (1-15)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 15), Array.Empty<object>()));
			ConfigEnableBreakShock = REPOShock.CFG.Bind<bool>("Settings_Items", "Enable_Breaking_Item_Shocks", true, "If enabled, will shock you whenever a valuable item you're holding takes damage.");
			ConfigMapValueToShock = REPOShock.CFG.Bind<bool>("Settings_Items", "Map_Value_to_Shock", false, "If enabled, shock intensity will be mapped to valuable objects original value.\nIf you break lose 250 durability on an item that started with 500, you would get\nshocked for 50% of your max intensity.");
			ConfigBreakValueDmgMult = REPOShock.CFG.Bind<float>("Settings_Items", "Value_Loss_Multiplier", 0.1f, new ConfigDescription("The intensity of the shock based on value lost. Damage an item for 200 value? at 0.1,\nthis will shock you for 20%. Caps at MaxIntensity", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
			ConfigBreakShockWindow = REPOShock.CFG.Bind<int>("Settings_Items", "Break_Shock_Window", 15, new ConfigDescription("The amount of time a Valuable Object you have touched is tied to you.\nIf it gets damaged during this period, you will get shocked.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), Array.Empty<object>()));
			ConfigBreakIgnoreEnemy = REPOShock.CFG.Bind<bool>("Settings_Items", "Ignore_Hitting_Enemies", true, "If enabled, hitting an enemy with an item won't shock you.");
			ThrownOffensiveGracePeriod = REPOShock.CFG.Bind<float>("Settings_Items", "Thrown_Offensive_Grace_Period", 2f, new ConfigDescription("The amount of time an object you let go of has to hit an enemy before it shocks you.\nThis also starts the HitOffensiveGracePeriod, so you can keep using it as a weapon.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>()));
			HitOffensiveGracePeriod = REPOShock.CFG.Bind<float>("Settings_Items", "Hit_Offensive_Grace_Period", 3f, new ConfigDescription("The amount of time an object you use as a weapon has before it causes you to get\nshocked again. After this times up, if you don't hit an enemy again, the item goes\nback to shocking you if it takes damage.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>()));
		}
	}
	internal static class ModGlobals
	{
		private static float _lastLevelMessage = Time.time;

		internal static GameObject? LastOffensiveObject = null;

		internal static float LastOffensiveGracePeriodTime = 0f;

		internal static Dictionary<GameObject, float> RecentlyHeldObjects = new Dictionary<GameObject, float>();

		internal static string SteamID { get; set; } = string.Empty;


		internal static bool IsAlive { get; set; } = true;


		internal static string CurrentLevel { get; set; } = "";


		internal static bool IsSafeLevel { get; set; } = true;


		internal static float LastHeldItemPickupTime { get; set; } = Time.time;


		internal static void UpdateLevel(string level)
		{
			CurrentLevel = level;
			EvaluateIsSafeLevel();
			if (!(_lastLevelMessage < 3f))
			{
				REPOShock.Logger.LogInfo((object)("Updating Level to " + level + " - Resetting Mod Global Variables"));
				_lastLevelMessage = Time.time;
			}
		}

		private static void EvaluateIsSafeLevel()
		{
			bool flag = false;
			if (!SemiFunc.IsMultiplayer())
			{
				flag = CurrentLevel == "Arena";
			}
			IsSafeLevel = CurrentLevel == "Shop" || CurrentLevel == "Lobby" || CurrentLevel == "Lobby Menu" || CurrentLevel == "" || flag;
		}

		internal static void Revive()
		{
			IsAlive = true;
			REPOShock.Logger.LogInfo((object)"Player has been revived.");
		}
	}
	internal class PiShockController
	{
		private PiShockAPI _piShockAPI;

		private PiShockUserInfo _userInfo;

		private AsyncPiShockRequestQueue _queue = new AsyncPiShockRequestQueue();

		public PiShockController(PiShockAPI piShockAPI, PiShockUserInfo piShockUserInfo)
		{
			_piShockAPI = piShockAPI;
			_userInfo = piShockUserInfo;
		}

		public static int ClampShock(int intensity)
		{
			return Math.Clamp(intensity, 1, ConfigHandler.ConfigMaxIntensity.Value);
		}

		public void OperatePiShock(int intensity, int duration, PiShockOperations op)
		{
			//IL_000e: 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)
			Task.Run(async delegate
			{
				List<Task<bool>> tasks = new List<Task<bool>>();
				LinqExtensions.ForEach<PiShockDeviceInfo>((IEnumerable<PiShockDeviceInfo>)_userInfo.Devices, (Action<PiShockDeviceInfo>)delegate(PiShockDeviceInfo device)
				{
					//IL_0016: 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_0042: Expected O, but got Unknown
					PiShockOperateRequestBody val = new PiShockOperateRequestBody(_userInfo, op, device.ShareCode, intensity, duration, "R.E.P.O Shock");
					tasks.Add(_piShockAPI.SendOperationRequest(val));
				});
				if ((await Task.WhenAll(tasks)).Any((bool success) => !success))
				{
					REPOShock.Logger.LogError((object)"One or more PiShock requests failed to execute!");
				}
			});
		}
	}
	[BepInPlugin("mxpuffin.REPOShock", "REPOShock", "1.0")]
	public class REPOShock : BaseUnityPlugin
	{
		internal static REPOShock Instance { get; private set; }

		internal static ConfigFile CFG { get; private set; }

		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private HttpClient? httpClient { get; set; }

		private PiShockAPI? PiShockAPI { get; set; }

		private PiShockUserInfo? PiShockUserInfo { get; set; }

		internal static PiShockController? PiShockController { get; set; }

		private async void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			CFG = ((BaseUnityPlugin)this).Config;
			httpClient = new HttpClient();
			PiShockAPI = new PiShockAPI(httpClient);
			PiShockLogger.LogInfoAction = Logger.LogInfo;
			PiShockLogger.LogErrorAction = Logger.LogError;
			await LoadConfigAndLogin();
			SteamId steamId = SteamClient.SteamId;
			ModGlobals.SteamID = ((object)(SteamId)(ref steamId)).ToString();
			Logger.LogInfo((object)("Setting steam ID for the session: " + ModGlobals.SteamID));
			Patch();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!");
		}

		internal void Patch()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
		}

		private async Task LoadConfigAndLogin()
		{
			ConfigHandler.InitConfig();
			ConfigEntry<string> val = CFG.Bind<string>("Auth_PiShock", "UserName", "", "Your PiShock username");
			ConfigEntry<string> val2 = CFG.Bind<string>("Auth_PiShock", "APIKey", "", "Your PiShock API Key");
			ConfigEntry<string> val3 = CFG.Bind<string>("Auth_PiShock", "ShareCodes", "", "Enter your sharecodes here! If you have multiple, you can separate them with commas like so \"abc123,def456\"");
			try
			{
				if (string.IsNullOrEmpty(val.Value) || string.IsNullOrEmpty(val2.Value))
				{
					throw new ArgumentException("Username and APIKey cannot be empty! Please add your Username and APIKey to the config!");
				}
				if (string.IsNullOrEmpty(val3.Value))
				{
					throw new ArgumentException("You have to enter atleast 1 Share Code! If you have multiple, you can separate them with commas like so \"abc123,def456\".");
				}
				string[] array = val3.Value.Split(',');
				List<PiShockDeviceInfo> devices = new List<PiShockDeviceInfo>();
				for (int i = 0; i < array.Length; i++)
				{
					devices.Add(new PiShockDeviceInfo($"Shocker {i}", array[i]));
				}
				Logger.LogInfo((object)val2.Value);
				PiShockUserInfo piShockUserInfo = (await PiShockAPI.GetUserInfoFromAPI(val.Value, val2.Value)).WithDevices(devices);
				PiShockController piShockController = new PiShockController(PiShockAPI, piShockUserInfo);
				PiShockUserInfo = piShockUserInfo;
				PiShockController = piShockController;
			}
			catch (Exception ex)
			{
				Logger.LogError((object)ex.ToString());
			}
		}
	}
}
namespace REPOShock.src.Patches
{
	[HarmonyPatch(typeof(PhysGrabObjectImpactDetector))]
	internal static class PhysGrabObjectImpactDetectorPatch
	{
		private static bool _lastHitEnemy;

		[HarmonyPrefix]
		[HarmonyPatch("OnCollisionStay")]
		private static void OnCollisionStay(ref Collision collision)
		{
			if (ConfigHandler.ConfigBreakIgnoreEnemy.Value)
			{
				if (((Component)collision.transform).CompareTag("Enemy"))
				{
					_lastHitEnemy = true;
				}
				else
				{
					_lastHitEnemy = false;
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("BreakRPC")]
		private static void ItemImpactBreakRPC(float valueLost, ref bool _loseValue, int breakLevel, PhysGrabObjectImpactDetector __instance)
		{
			if (ConfigHandler.ConfigEnableBreakShock.Value && _loseValue)
			{
				ItemBreakPostfix(valueLost, breakLevel, __instance);
			}
		}

		private static void ItemBreakPostfix(float valueLost, int breakLevel, PhysGrabObjectImpactDetector __instance)
		{
			if (!ModGlobals.IsAlive || ModGlobals.CurrentLevel == "Arena" || !__instance.isValuable)
			{
				return;
			}
			bool heldByLocalPlayer = __instance.physGrabObject.heldByLocalPlayer;
			if (heldByLocalPlayer)
			{
				if (!ModGlobals.RecentlyHeldObjects.ContainsKey(((Component)__instance).gameObject))
				{
					ModGlobals.RecentlyHeldObjects.Add(((Component)__instance).gameObject, Time.time);
					REPOShock.Logger.LogInfo((object)("Object " + ((Object)((Component)__instance).gameObject).name + " added to held objects."));
				}
				else
				{
					ModGlobals.RecentlyHeldObjects[((Component)__instance).gameObject] = Time.time;
				}
			}
			float dollarValueOriginal = __instance.valuableObject.dollarValueOriginal;
			ShockPlayerIfNecessary(((Component)__instance).gameObject, valueLost, dollarValueOriginal, heldByLocalPlayer);
		}

		private static void ShockPlayerIfNecessary(GameObject damagedObject, float valueLost, float originalValue, bool isHeldByLocalPlayer)
		{
			if (ModGlobals.RecentlyHeldObjects.ContainsKey(damagedObject))
			{
				float num = ModGlobals.RecentlyHeldObjects[damagedObject];
				if (isHeldByLocalPlayer && _lastHitEnemy)
				{
					ModGlobals.LastOffensiveGracePeriodTime = Time.time;
					ModGlobals.LastOffensiveObject = damagedObject;
					REPOShock.Logger.LogInfo((object)"[Damage Item Event] Enemy hit, aborting shock");
				}
				else if (_lastHitEnemy && Time.time - num <= ConfigHandler.ThrownOffensiveGracePeriod.Value)
				{
					ModGlobals.LastOffensiveGracePeriodTime = Time.time;
					ModGlobals.LastOffensiveObject = damagedObject;
					REPOShock.Logger.LogInfo((object)"[Damage Item Event] Enemy hit in thrown grace period, aborting shock");
				}
				else if ((Object)(object)ModGlobals.LastOffensiveObject == (Object)(object)damagedObject && Time.time - ModGlobals.LastOffensiveGracePeriodTime <= ConfigHandler.HitOffensiveGracePeriod.Value)
				{
					REPOShock.Logger.LogInfo((object)"[Damage Item Event] Recently used 'weapon' took damage, aborting shock");
				}
				else if (Time.time - num <= (float)ConfigHandler.ConfigBreakShockWindow.Value)
				{
					ShockPlayer(valueLost, originalValue, ((Object)damagedObject).name);
				}
			}
		}

		private static void ShockPlayer(float valueLost, float originalValue, string objName)
		{
			int intensity = (ConfigHandler.ConfigMapValueToShock.Value ? MapValue(valueLost, 0f, originalValue, 0f, ConfigHandler.ConfigMaxIntensity.Value) : ((int)Math.Ceiling(valueLost * ConfigHandler.ConfigBreakValueDmgMult.Value)));
			intensity = PiShockController.ClampShock(intensity);
			REPOShock.PiShockController?.OperatePiShock(intensity, 1, (PiShockOperations)0);
			REPOShock.Logger.LogInfo((object)$"[Damage Item Event] Played damaged {objName} for {valueLost} - Shocking for {intensity}%");
		}

		private static int MapValue(float value, float start1, float stop1, float start2, float stop2)
		{
			return (int)(start1 + (value - start1) * (stop2 - start2) / (stop1 - start1));
		}
	}
	[HarmonyPatch(typeof(PhysGrabObject))]
	public static class PhysGrabObjectPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("GrabEnded")]
		private static void GrabEndedPostFix(PhysGrabObject __instance)
		{
			if (__instance.isValuable && __instance.heldByLocalPlayer)
			{
				ModGlobals.LastHeldItemPickupTime = Time.time;
				ModGlobals.LastOffensiveGracePeriodTime = 0f;
				if (!ModGlobals.RecentlyHeldObjects.ContainsKey(((Component)__instance).gameObject))
				{
					ModGlobals.RecentlyHeldObjects.Add(((Component)__instance).gameObject, Time.time);
					REPOShock.Logger.LogInfo((object)("Added " + ((Object)((Component)__instance).gameObject).name + " to recently held"));
				}
				else
				{
					ModGlobals.RecentlyHeldObjects[((Component)__instance).gameObject] = Time.time;
					REPOShock.Logger.LogInfo((object)("Updated " + ((Object)((Component)__instance).gameObject).name + " in recently held"));
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar))]
	internal static class PlayerAvatarPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("PlayerDeathRPC")]
		private static void PlayerDeathRPC(PlayerAvatar __instance)
		{
			if (!(SemiFunc.PlayerGetSteamID(__instance) != ModGlobals.SteamID))
			{
				PlayerDeath();
			}
		}

		private static void PlayerDeath()
		{
			if (!ModGlobals.IsSafeLevel)
			{
				ModGlobals.IsAlive = false;
				int value = ConfigHandler.ConfigDeathIntensity.Value;
				int value2 = ConfigHandler.ConfigDeathDuration.Value;
				REPOShock.PiShockController?.OperatePiShock(value, value2, (PiShockOperations)0);
				REPOShock.Logger.LogInfo((object)$"Player died, shocking with {value}% for {value2}s");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("Revive")]
		private static void PlayerRevive()
		{
			ModGlobals.Revive();
		}

		[HarmonyPostfix]
		[HarmonyPatch("ReviveRPC")]
		private static void ReviveRPC()
		{
			ModGlobals.Revive();
		}
	}
	[HarmonyPatch(typeof(PlayerHealth))]
	internal static class PlayerHealthPatch
	{
		[HarmonyPrefix]
		[HarmonyPatch("Hurt")]
		private static void TakeDamage(ref int damage, PlayerHealth __instance)
		{
			REPOShock.Logger.LogInfo((object)SemiFunc.PlayerGetSteamID(__instance.playerAvatar));
			REPOShock.Logger.LogInfo((object)ModGlobals.SteamID);
			if (!(SemiFunc.PlayerGetSteamID(__instance.playerAvatar) != ModGlobals.SteamID))
			{
				REPOShock.Logger.LogInfo((object)$"[Health Event]: Took Damage: {damage}. Is Safe: {ModGlobals.IsSafeLevel}. Is Alive?: {ModGlobals.IsAlive}");
				if (!ModGlobals.IsSafeLevel && ModGlobals.IsAlive && damage != 0)
				{
					int intensity = (int)Math.Ceiling((float)damage * ConfigHandler.ConfigDamageInteravl.Value);
					intensity = PiShockController.ClampShock(intensity);
					REPOShock.PiShockController?.OperatePiShock(intensity, 1, (PiShockOperations)0);
					REPOShock.Logger.LogInfo((object)$"[Health Event] Took {damage} damage, shocking for {intensity}%");
				}
			}
		}
	}
	[HarmonyPatch(typeof(RunManager))]
	internal static class RunManagerPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("ChangeLevel")]
		public static void ChangeLevel(RunManager __instance)
		{
			UpdateLevelInfoAndResetState(((Object)__instance.levelCurrent).name);
		}

		[HarmonyPostfix]
		[HarmonyPatch("UpdateLevel")]
		public static void UpdateLevel(RunManager __instance, ref string _levelName, ref bool _gameOver)
		{
			UpdateLevelInfoAndResetState(_levelName);
		}

		private static void UpdateLevelInfoAndResetState(string level)
		{
			string text = level.Replace("Level - ", "");
			ModGlobals.UpdateLevel(text.Trim());
			ModGlobals.RecentlyHeldObjects.Clear();
			if (!ModGlobals.IsAlive)
			{
				ModGlobals.Revive();
			}
		}
	}
}

BepInEx/plugins/mxpuffin-REPOShock/PiShockClassLibrary.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PiShockClassLibrary.Interfaces;
using PiShockClassLibrary.Models;
using PiShockLibrary.Enums;
using PiShockLibrary.Exceptions;
using PiShockLibrary.Extensions;
using PiShockLibrary.Models;
using PiShockLibrary.Utilities;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("PiShockLibrary_NetFramework4.7.2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("mxpuffin")]
[assembly: AssemblyProduct("PiShockLibrary_NetFramework4.7.2")]
[assembly: AssemblyCopyright("Copyright ©mxpuffin  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("8567ba7d-6e96-431a-8dc4-6b7d19c06e5c")]
[assembly: AssemblyFileVersion("1.0.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.1")]
namespace PiShockLibrary.Utilities
{
	public static class PiShockLogger
	{
		public static Action<string> LogInfoAction { get; set; }

		public static Action<string> LogErrorAction { get; set; }

		public static void Log(string message)
		{
			LogInfoAction?.Invoke("[PiShock] " + message);
		}

		public static void LogError(string message)
		{
			LogErrorAction?.Invoke("[PiShock] " + message);
		}
	}
}
namespace PiShockLibrary.Models
{
	public class PiShockOperateRequestBody
	{
		public string UserName { get; private set; }

		public string APIKey { get; private set; }

		[JsonProperty("op")]
		public PiShockOperations Operation { get; private set; }

		[JsonProperty("code")]
		public string ShareCode { get; private set; }

		public int Intensity { get; private set; }

		public int Duration { get; private set; }

		[JsonProperty("name")]
		public string LogName { get; private set; } = "PiShock C# Library";


		public bool Random { get; private set; }

		public bool Scale { get; private set; }

		public PiShockOperateRequestBody(PiShockUserInfo userInfo, PiShockOperations operation, int shockerIndex, int intensity, int duration, string logName = null)
		{
			UserName = userInfo.Username;
			APIKey = userInfo.ApiKey;
			Operation = operation;
			ShareCode = userInfo.Devices[shockerIndex].ShareCode ?? throw new NullReferenceException("Share Code Index provided was null");
			Intensity = intensity;
			Duration = duration;
			LogName = logName ?? LogName;
		}

		public PiShockOperateRequestBody(PiShockUserInfo userInfo, PiShockOperations operation, string shareCode, int intensity, int duration, string logName = null)
		{
			UserName = userInfo.Username;
			APIKey = userInfo.ApiKey;
			Operation = operation;
			ShareCode = shareCode;
			Intensity = intensity;
			Duration = duration;
			LogName = logName ?? LogName;
		}
	}
}
namespace PiShockLibrary.Exceptions
{
	internal class PiShockAPIException : Exception
	{
		public PiShockAPIException(string message, Exception innerException = null)
			: base(message, innerException)
		{
		}
	}
	internal class PiShockDeviceNotFoundException : Exception
	{
		public PiShockDeviceNotFoundException(string message, Exception innerException = null)
			: base(message, innerException)
		{
		}
	}
}
namespace PiShockLibrary.Extensions
{
	internal static class HttpResponseMessageExtensions
	{
		internal static async Task<string> EnsureSuccessAndReturnStringBody(this HttpResponseMessage response)
		{
			response.EnsureSuccessStatusCode();
			string obj = await response.Content.ReadAsStringAsync();
			if (string.IsNullOrEmpty(obj))
			{
				throw new PiShockAPIException("Invalid API request. Response body returned empty.");
			}
			return obj;
		}
	}
}
namespace PiShockLibrary.Enums
{
	public enum PiShockOperations
	{
		Shock,
		Vibrate,
		Beep
	}
}
namespace PiShockClassLibrary.Services
{
	public class PiShockAPI : IPiShockAPI
	{
		private readonly HttpClient _httpClient;

		public PiShockAPI(HttpClient httpClient)
		{
			_httpClient = httpClient;
		}

		public async Task<PiShockUserInfo> GetUserInfoFromAPI(string username, string apiKey)
		{
			PiShockLogger.Log("Attempting to Login to the PiShock Network!");
			try
			{
				PiShockUserInfo piShockUserInfo = JsonConvert.DeserializeObject<PiShockUserInfo>(await (await _httpClient.GetAsync("https://auth.pishock.com/Auth/GetUserIfAPIKeyValid/?apiKey=" + apiKey + "&username=" + username)).EnsureSuccessAndReturnStringBody());
				if (piShockUserInfo == null)
				{
					throw new NullReferenceException("Failed to deserialize login information");
				}
				PiShockLogger.Log("Login Successful!: " + piShockUserInfo.Username);
				return new PiShockUserInfo(piShockUserInfo.ClientID, username, apiKey);
			}
			catch (PiShockAPIException ex)
			{
				PiShockLogger.LogError(ex.Message);
				throw new PiShockAPIException("Error fetching PiShock user info. Potentially Invalid Credentials.");
			}
			catch (Exception ex2)
			{
				PiShockLogger.LogError(ex2.Message);
				throw;
			}
		}

		public async Task<int[]> GetShareIDsByOwner(PiShockUserInfo userInfo)
		{
			PiShockLogger.Log("Attempting to Fetch Share IDs!");
			PiShockLogger.Log("GetShareIDsByOwner Username: " + userInfo.Username);
			JToken val = JToken.Parse(await (await _httpClient.GetAsync(string.Format("{0}?UserId={1}&Token={2}&api=true", "https://ps.pishock.com/PiShock/GetShareCodesByOwner", userInfo.ClientID, userInfo.ApiKey))).EnsureSuccessAndReturnStringBody());
			if (val[(object)userInfo.Username] != null)
			{
				JToken obj = val[(object)userInfo.Username];
				int[] result = ((obj != null) ? obj.ToObject<int[]>() : null);
				PiShockLogger.Log("Share ID's Fetched Successfully!");
				return result;
			}
			throw new PiShockDeviceNotFoundException("Could not fetch Share IDs for '" + userInfo.Username + "'.");
		}

		public async Task<PiShockUserInfo> GetShockerShareCodesByID(PiShockUserInfo userInfo)
		{
			int[] array = await GetShareIDsByOwner(userInfo);
			string text = string.Join("", array.Select((int id) => $"&shareIds={id}"));
			PiShockLogger.Log(string.Format("Attempting to retrieve {0} ShareCode{1}!", array.Length, (array.Length > 1) ? "s" : ""));
			try
			{
				JToken obj = JToken.Parse(await (await _httpClient.GetAsync(string.Format("{0}?UserId={1}&Token={2}&api=true{3}", "https://ps.pishock.com/PiShock/GetShockersByShareIds", userInfo.ClientID, userInfo.ApiKey, text))).EnsureSuccessAndReturnStringBody())[(object)userInfo.Username];
				List<PiShockDeviceInfo> list = ((obj != null) ? obj.ToObject<List<PiShockDeviceInfo>>() : null);
				if (list == null)
				{
					throw new PiShockDeviceNotFoundException("Failed to deserialize share codes from response");
				}
				PiShockLogger.Log("Share Codes retrieved successfully!");
				return userInfo.WithDevices(list);
			}
			catch (PiShockDeviceNotFoundException)
			{
				throw;
			}
			catch (Exception ex2)
			{
				PiShockLogger.LogError(ex2.Message);
				throw;
			}
		}

		public async Task<PiShockUserInfo> GetUserInfoAndShareCodes(string username, string apiKey)
		{
			return await GetShockerShareCodesByID(await GetUserInfoFromAPI(username, apiKey));
		}

		public async Task<bool> SendOperationRequest(PiShockOperateRequestBody requestBody)
		{
			StringContent content = null;
			try
			{
				string text = JsonConvert.SerializeObject((object)requestBody);
				content = new StringContent(text, Encoding.UTF8, "application/json");
				string text2 = await (await _httpClient.PostAsync("https://ps.pishock.com/PiShock/operate", (HttpContent)(object)content)).EnsureSuccessAndReturnStringBody();
				if (text2 != "\"Operation Attempted.\"")
				{
					throw new PiShockAPIException("Invalid PiShock Request: " + text2);
				}
				return text2 != null;
			}
			catch (Exception ex)
			{
				PiShockLogger.LogError("Error in SendOperationRequest: " + ex.Message + "\n" + ex.StackTrace);
				return false;
			}
			finally
			{
				StringContent obj = content;
				if (obj != null)
				{
					((HttpContent)obj).Dispose();
				}
			}
		}
	}
}
namespace PiShockClassLibrary.Models
{
	public class PiShockDeviceInfo
	{
		public string ShockerName { get; private set; }

		public string ShareCode { get; private set; }

		public int ShockerId { get; private set; }

		public bool IsEnabled { get; private set; }

		public PiShockDeviceInfo(string shockerName, string shareCode)
		{
			ShockerName = shockerName;
			ShareCode = shareCode;
			ShockerId = -1;
			IsEnabled = true;
		}
	}
	public class PiShockUserInfo
	{
		public int ClientID { get; set; }

		public string Username { get; set; }

		[JsonIgnore]
		public string ApiKey { get; set; }

		public IReadOnlyList<PiShockDeviceInfo> Devices { get; set; }

		public PiShockUserInfo(int clientId, string username, string apiKey = null, List<PiShockDeviceInfo> devices = null)
		{
			ClientID = clientId;
			Username = username ?? string.Empty;
			ApiKey = apiKey ?? string.Empty;
			Devices = devices?.AsReadOnly() ?? new List<PiShockDeviceInfo>().AsReadOnly();
		}

		public PiShockUserInfo WithDevices(List<PiShockDeviceInfo> devices)
		{
			return new PiShockUserInfo(ClientID, Username, ApiKey, devices);
		}
	}
}
namespace PiShockClassLibrary.Interfaces
{
	public interface IPiShockAPI
	{
		Task<PiShockUserInfo> GetUserInfoFromAPI(string username, string apiKey);

		Task<int[]> GetShareIDsByOwner(PiShockUserInfo userInfo);

		Task<PiShockUserInfo> GetShockerShareCodesByID(PiShockUserInfo userInfo);

		Task<PiShockUserInfo> GetUserInfoAndShareCodes(string username, string apiKey);

		Task<bool> SendOperationRequest(PiShockOperateRequestBody requestBody);
	}
}
namespace PiShockClassLibrary.Constants
{
	internal class ApiEndpoints
	{
		public const string BaseUrl = "https://ps.pishock.com/PiShock/";

		public const string AuthUrl = "https://auth.pishock.com/Auth/GetUserIfAPIKeyValid/";

		public const string GetShareCodesByOwner = "https://ps.pishock.com/PiShock/GetShareCodesByOwner";

		public const string GetShockersByShareIds = "https://ps.pishock.com/PiShock/GetShockersByShareIds";

		public const string Operate = "https://ps.pishock.com/PiShock/operate";
	}
}