Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of OWO Haptics v1.0.0
OWO_REPO.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using HarmonyLib; using OWOGame; using Photon.Pun; 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("OWO_REPO")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("OWO_REPO")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("a983420a-71d7-4942-99f5-c1daf934b47d")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace OWO_REPO; public class OWOInteractables { [HarmonyPatch(typeof(PropaneTankTrap), "Explode")] public class OnPropaneTankTrapExplode { [HarmonyPostfix] public static void Postfix(PropaneTankTrap __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(BarrelValuable), "Explode")] public class OnBarrelValuableExplode { [HarmonyPostfix] public static void Postfix(BarrelValuable __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(FlamethrowerValuable), "Explode")] public class OnFlamethrowerValuableExplode { [HarmonyPostfix] public static void Postfix(FlamethrowerValuable __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(PowerCrystalValuable), "Explode")] public class OnPowerCrystalValuableExplode { [HarmonyPostfix] public static void Postfix(PowerCrystalValuable __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ToiletFun), "Explosion")] public class OnToiletFunExplosion { [HarmonyPostfix] public static void Postfix(ToiletFun __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemMeleeInflatableHammer), "ExplosionRPC")] public class OnItemMeleeInflatableHammerExplosionRPC { [HarmonyPostfix] public static void Postfix(ItemMeleeInflatableHammer __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemMine), "StateTriggered")] public class OnStateTriggered { [HarmonyPostfix] public static void Postfix(ItemMine __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemGrenadeDuctTaped), "Explosion")] public class OnItemGrenadeDuctTapedExplosion { [HarmonyPostfix] public static void Postfix(ItemGrenadeDuctTaped __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemGrenadeExplosive), "Explosion")] public class OnItemGrenadeExplosiveExplosion { [HarmonyPostfix] public static void Postfix(ItemGrenadeExplosive __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemGrenadeHuman), "Explosion")] public class OnItemGrenadeHumanExplosion { [HarmonyPostfix] public static void Postfix(ItemGrenadeHuman __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemGrenadeShockwave), "Explosion")] public class OnItemGrenadeShockwave { [HarmonyPostfix] public static void Postfix(ItemGrenadeShockwave __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(ItemGrenadeStun), "Explosion")] public class OnItemGrenadeStun { [HarmonyPostfix] public static void Postfix(ItemGrenadeStun __instance) { RecieveExplosion((MonoBehaviour)(object)__instance); } } [HarmonyPatch(typeof(Cauldron), "Explosion")] public class OnExplode { [HarmonyPostfix] public static void Postfix(Cauldron __instance) { if (owoSkin.CanFeel()) { RecieveExplosion((MonoBehaviour)(object)__instance); } } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")] public class OnBreakRPC { [HarmonyPostfix] public static void PostFix(PhysGrabObjectImpactDetector __instance, float valueLost, Vector3 _contactPoint, int breakLevel, bool _loseValue) { if (owoSkin.CanFeel() || _loseValue) { PhysGrabObject value = Traverse.Create((object)__instance).Field("physGrabObject").GetValue<PhysGrabObject>(); if (Traverse.Create((object)value).Field("heldByLocalPlayer").GetValue<bool>()) { owoSkin.Feel("Object Break", 3, Mathf.Clamp((int)(valueLost / 500f) * 100, 25, 100)); } } } } [HarmonyPatch(typeof(ItemGun), "ShootRPC")] public class OnShootRPC { [HarmonyPostfix] public static void PostFix(ItemGun __instance) { if (!owoSkin.CanFeel()) { return; } bool flag = false; PhysGrabObject value = Traverse.Create((object)__instance).Field("physGrabObject").GetValue<PhysGrabObject>(); List<PhysGrabber> value2 = Traverse.Create((object)value).Field("playerGrabbing").GetValue<List<PhysGrabber>>(); if (GameManager.Multiplayer()) { foreach (PhysGrabber item in value2) { if (item.photonView.IsMine) { flag = true; } } } else if (value2.Count > 0) { flag = true; } if (flag) { owoSkin.Feel("Recoil", 3); } } } private static OWOSkin owoSkin; public static float explosionDistance = 10f; public OWOInteractables(OWOSkin owoSkinGiven, float explosionReference) { owoSkin = owoSkinGiven; explosionDistance = explosionReference; } public static int IsLocalPlayerNear(float range, Vector3 position) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (player.photonView.IsMine || !GameManager.Multiplayer()) { Vector3 position2 = player.PlayerVisionTarget.VisionTransform.position; float num = Vector3.Distance(position, position2); if (num > range) { return -1; } return (int)num; } } return -1; } public static void RecieveExplosion(MonoBehaviour __instance) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (owoSkin.CanFeel()) { int num = IsLocalPlayerNear(explosionDistance, ((Component)__instance).transform.position); if (num >= 0) { owoSkin.Feel("Explosion", 3, Mathf.Clamp((num + 10 - num * 2) * 10, 30, 100)); } } } } [BepInPlugin("org.bepinex.plugins.OWO_REPO", "OWO_REPO", "1.0.0")] public class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(PlayerController), "ChangeState")] public class OnChangeState { [HarmonyPostfix] public static void Postfix(PlayerController __instance) { if (owoSkin.CanFeel()) { if (__instance.Crouching && lastPlayerState != "crouching") { lastPlayerState = "crouching"; owoSkin.Feel("Crouch", 2); } if (!__instance.Crouching && !__instance.Crawling) { lastPlayerState = "standing"; } } } } [HarmonyPatch(typeof(PlayerController), "Revive")] public class OnRevive { [HarmonyPostfix] public static void Postfix(PlayerController __instance) { bool isMine = __instance.playerAvatar.GetComponent<PlayerAvatar>().photonView.IsMine; if (!owoSkin.playing && (isMine || !GameManager.Multiplayer())) { owoSkin.playing = true; owoSkin.Feel("Revive", 3); } } } [HarmonyPatch(typeof(PlayerHealth), "Hurt")] public class OnHurt { [HarmonyPostfix] public static void Postfix(PlayerHealth __instance, int damage, bool savingGrace, int enemyIndex = -1) { if (owoSkin.CanFeel()) { PhotonView value = Traverse.Create((object)__instance).Field("photonView").GetValue<PhotonView>(); if (damage > 0 && (value.IsMine || !GameManager.Multiplayer())) { owoSkin.Feel("Hurt", 3, Mathf.Clamp(damage / 70 * 100, 30, 100)); } } } } [HarmonyPatch(typeof(PlayerHealth), "HurtOther")] public class OnHurtOther { [HarmonyPostfix] public static void Postfix(PlayerHealth __instance, int damage, Vector3 hurtPosition, bool savingGrace, int enemyIndex = -1) { if (owoSkin.CanFeel() || GameManager.Multiplayer()) { PhotonView value = Traverse.Create((object)__instance).Field("photonView").GetValue<PhotonView>(); if (value.IsMine) { owoSkin.Feel("Hurt", 3, 30); } } } } [HarmonyPatch(typeof(PlayerHealth), "Heal")] public class OnHeal { [HarmonyPostfix] public static void Postfix(int healAmount, bool effect = true) { if (owoSkin.CanFeel()) { owoSkin.Feel("Heal", 2); } } } [HarmonyPatch(typeof(PlayerHealth), "Death")] public class OnDeath { [HarmonyPostfix] public static void Postfix(PlayerHealth __instance) { PlayerAvatar value = Traverse.Create((object)__instance).Field("playerAvatar").GetValue<PlayerAvatar>(); if (owoSkin.CanFeel() && (value.photonView.IsMine || !GameManager.Multiplayer())) { owoSkin.playing = false; owoSkin.StopAllHapticFeedback(); owoSkin.Feel("Death", 4); } } } [HarmonyPatch(typeof(CameraJump), "Jump")] public class OnJump { [HarmonyPostfix] public static void Postfix() { if (owoSkin.CanFeel()) { owoSkin.Feel("Jump", 2); } } } [HarmonyPatch(typeof(CameraJump), "Land")] public class OnLand { [HarmonyPostfix] public static void Postfix() { if (owoSkin.CanFeel()) { owoSkin.Feel("Landing", 2); } } } [HarmonyPatch(typeof(CameraGlitch), "PlayUpgrade")] public class OnPlayUpgrade { [HarmonyPostfix] public static void Postfix() { if (owoSkin.CanFeel()) { owoSkin.Feel("Upgrade", 2); } } } [HarmonyPatch(typeof(PhysGrabber), "PhysGrabStartEffects")] public class OnPhysGrabStartEffects { [HarmonyPostfix] public static void Postfix(PhysGrabber __instance) { PhysGrabObject value = Traverse.Create((object)__instance).Field("grabbedPhysGrabObject").GetValue<PhysGrabObject>(); if (__instance.isLocal && owoSkin.CanFeel()) { owoSkin.BeamIntensity(Object.op_Implicit((Object)(object)value) ? value.rb.mass : 0.5f); owoSkin.StartBeam(); } } } [HarmonyPatch(typeof(PhysGrabber), "PhysGrabEndEffects")] public class OnPhysGrabEndEffects { [HarmonyPostfix] public static void Postfix(PhysGrabber __instance) { if (__instance.isLocal && owoSkin.CanFeel()) { owoSkin.StopBeam(); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] public class OnChangeLevel { [HarmonyPostfix] public static void Postfix(RunManager __instance) { if (owoSkin.suitEnabled) { if ((Object)(object)__instance.levelCurrent != (Object)(object)__instance.levelLobbyMenu && !owoSkin.playing) { owoSkin.playing = true; } else if (owoSkin.playing) { owoSkin.playing = false; owoSkin.StopAllHapticFeedback(); } } } } [HarmonyPatch(typeof(RunManager), "UpdateLevel")] public class OnUpdateLevel { [HarmonyPostfix] public static void Postfix(RunManager __instance) { if (owoSkin.suitEnabled) { if ((Object)(object)__instance.levelCurrent != (Object)(object)__instance.levelLobbyMenu && !owoSkin.playing) { owoSkin.playing = true; } else if (owoSkin.playing) { owoSkin.playing = false; owoSkin.StopAllHapticFeedback(); } } } } [HarmonyPatch(typeof(RunManager), "LeaveToMainMenu")] public class OnLeaveToMainMenu { [HarmonyPostfix] public static void Postfix(RunManager __instance) { if (owoSkin.suitEnabled && owoSkin.playing) { owoSkin.playing = false; owoSkin.StopAllHapticFeedback(); } } } internal static ManualLogSource Log; public static OWOSkin owoSkin; public static OWOInteractables interactables; public static float explosionDistance = 10f; public static string lastPlayerState = ""; private void Awake() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ((BaseUnityPlugin)this).Logger.LogMessage((object)"OWO_REPO plugin is loaded!"); owoSkin = new OWOSkin(); interactables = new OWOInteractables(owoSkin, explosionDistance); Harmony val = new Harmony("owo.patch.repo"); val.PatchAll(); } public static bool IsLocalPlayerNear(float range, Vector3 position) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (Traverse.Create((object)player).Field("IsLocal").GetValue<bool>()) { Vector3 position2 = player.PlayerVisionTarget.VisionTransform.position; float num = Vector3.Distance(position, position2); if (num > range) { return false; } return true; } } return false; } } public class OWOSkin { public bool suitEnabled = false; public bool playing = false; public bool beamIsActive = false; private int beamIntensity = 30; public Dictionary<string, Sensation> FeedbackMap = new Dictionary<string, Sensation>(); public OWOSkin() { RegisterAllSensationsFiles(); InitializeOWO(); } private void RegisterAllSensationsFiles() { string path = Directory.GetCurrentDirectory() + "\\BepinEx\\Plugins\\OWO"; DirectoryInfo directoryInfo = new DirectoryInfo(path); FileInfo[] files = directoryInfo.GetFiles("*.owo", SearchOption.AllDirectories); for (int i = 0; i < files.Length; i++) { string name = files[i].Name; string fullName = files[i].FullName; string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(name); if (!(name == ".") && !(name == "..")) { string text = File.ReadAllText(fullName); try { Sensation value = Sensation.Parse(text); FeedbackMap.Add(fileNameWithoutExtension, value); } catch (Exception ex) { LOG(ex.Message); } } } } private async void InitializeOWO() { LOG("Initializing OWO skin"); GameAuth gameAuth = GameAuth.Create(AllBakedSensations()).WithId("14534911"); OWO.Configure(gameAuth); string[] myIPs = GetIPsFromFile("OWO_Manual_IP.txt"); if (myIPs.Length != 0) { await OWO.Connect(myIPs); } else { await OWO.AutoConnect(); } if ((int)OWO.ConnectionState == 0) { suitEnabled = true; LOG("OWO suit connected."); Feel("Heart Beat"); } if (!suitEnabled) { LOG("OWO is not enabled?!?!"); } } public BakedSensation[] AllBakedSensations() { List<BakedSensation> list = new List<BakedSensation>(); foreach (Sensation value in FeedbackMap.Values) { BakedSensation val = (BakedSensation)(object)((value is BakedSensation) ? value : null); if (val != null) { LOG("Registered baked sensation: " + val.name); list.Add(val); } else { LOG("Sensation not baked? " + Sensation.op_Implicit(value)); } } return list.ToArray(); } public string[] GetIPsFromFile(string filename) { List<string> list = new List<string>(); string text = Directory.GetCurrentDirectory() + "\\BepinEx\\Plugins\\OWO" + filename; if (File.Exists(text)) { LOG("Manual IP file found: " + text); IEnumerable<string> enumerable = File.ReadLines(text); foreach (string item in enumerable) { if (IPAddress.TryParse(item, out IPAddress _)) { list.Add(item); } else { LOG("IP not valid? ---" + item + "---"); } } } return list.ToArray(); } ~OWOSkin() { LOG("Destructor called"); DisconnectOWO(); } public void DisconnectOWO() { LOG("Disconnecting OWO skin."); OWO.Disconnect(); } public void LOG(string msg) { Plugin.Log.LogInfo((object)msg); } public void Feel(string key, int Priority = 0, int intensity = 0) { if (FeedbackMap.ContainsKey(key)) { Sensation val = FeedbackMap[key]; if (intensity != 0) { val = SensationExtensions.WithMuscles(val, MusclesExtensions.WithIntensity(Muscle.All, intensity)); } OWO.Send(SensationExtensions.WithPriority(val, Priority), Array.Empty<Muscle>()); } else { LOG("Feedback not registered: " + key); } } public void BeamIntensity(float objectMass) { beamIntensity = (int)Mathf.Clamp(objectMass / 7f * 100f, 40f, 100f); } public void StartBeam() { if (!beamIsActive) { beamIsActive = true; BeamFuncAsync(); } } public void StopBeam() { beamIsActive = false; } public async Task BeamFuncAsync() { while (beamIsActive) { Feel("Grab Beam", 0, beamIntensity); await Task.Delay(200); } } public void StopAllHapticFeedback() { StopBeam(); OWO.Stop(); } public bool CanFeel() { return suitEnabled && playing; } }
OWO.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using OWOGame.Controller; using OWOGame.Infraestructure; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: InternalsVisibleTo("Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("OWO")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("OWO SDK for CSharp projects")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0e45093aee9899b77a718013fa020523d331f5b1")] [assembly: AssemblyProduct("OWO")] [assembly: AssemblyTitle("OWO")] [assembly: AssemblyVersion("1.0.0.0")] namespace OWOGame { public class OWO { private static OWO instance; private readonly Client client; private readonly SendSensation send; private readonly StopSensation stop; private readonly Disconnect disconnect; private readonly Connect connect; private readonly RealTimeClock clock; private static OWO Instance => instance ?? (instance = new OWO(ClientFactory.Create(new UDPNetwork()), new RealTimeClock())); public static ConnectionState ConnectionState => Instance.client.State; public static string[] DiscoveredApps => Instance.client.DiscoveredServers.ToArray(); internal OWO(Client client, RealTimeClock clock) { this.client = client; send = new SendSensation(client); stop = new StopSensation(client); connect = new Connect(client); disconnect = new Disconnect(client); this.clock = clock; } public static void Configure(GameAuth game) { Instance.ConfigureB(game); } internal void ConfigureB(GameAuth game) { connect.Configure(game); send.Configure(game); stop.Configure(game); } public static Task AutoConnect() { return Instance.AutoConnectB(); } internal Task AutoConnectB() { return Task.Run((Func<Task?>)connect.AutoConnect); } public static void StartScan() { Instance.StartScanB(); } internal void StartScanB() { Task.Run((Func<Task?>)connect.ScanServer); } public static Task Connect(params string[] ips) { return Instance.ConnectB(ips); } internal Task ConnectB(params string[] ips) { return Task.Run(() => connect.ManualConnect(ips)); } public static void Stop() { Instance.StopB(); } internal void StopB() { stop.Execute(); send.ResetPriority(); } public static void Send(Sensation sensation, params Muscle[] muscles) { Instance.SendB(sensation, muscles); } internal void SendB(Sensation sensation, params Muscle[] muscles) { send.Execute(sensation.WithMuscles(muscles), clock.TotalMilliseconds); } public static void Disconnect() { Instance.DisconnectB(); } internal void DisconnectB() { disconnect.Execute(); } } public class BakedSensation : Sensation { public readonly int id; public readonly string name; public readonly Family Family; public readonly Sensation reference; public readonly Icon icon; public override float Duration => reference.Duration; internal BakedSensation(int id, string name, Sensation reference, Icon icon, Family family) { this.id = id; this.name = name; this.reference = reference; this.icon = icon; Family = family; } public new static BakedSensation Parse(string message) { return ((Sensation)message) as BakedSensation; } public override Sensation MultiplyIntensityBy(Multiplier howMuch) { return new BakedSensation(id, name, reference, icon, Family); } public BakedSensation WithIcon(Icon icon) { return new BakedSensation(id, name, reference, icon, Family).WithPriority(base.Priority) as BakedSensation; } public BakedSensation BelongsTo(Family family) { return new BakedSensation(id, name, reference, icon, family).WithPriority(base.Priority) as BakedSensation; } public string Stringify() { return BakedSensationsBuilder.Stringify(this); } } internal static class BakedSensationsBuilder { private const string SEPARATOR = "~"; public static string From(BakedSensation sensation) { int id = sensation.id; return id.ToString(); } public static string Stringify(BakedSensation sensation) { string[] array = new string[9]; int id = sensation.id; array[0] = id.ToString(); array[1] = "~"; array[2] = sensation.name; array[3] = "~"; array[4] = sensation.reference; array[5] = "~"; array[6] = sensation.icon; array[7] = "~"; array[8] = sensation.Family; return string.Concat(array); } } internal static class GamesBuilder { private const string SEPARATOR = "#"; public static string Build(GameAuth theGame) { if (theGame.sensations.Length == 0) { return string.Empty; } string text = theGame.sensations[0].Stringify(); for (int i = 1; i < theGame.sensations.Length; i++) { text = text + "#\n" + theGame.sensations[i].Stringify(); } return text; } } internal static class MusclesBuilder { public static string From(params Muscle[] muscles) { string text = From(muscles[0]); for (int i = 1; i < muscles.Length; i++) { text = text + "," + From(muscles[i]); } return text; } private static string From(Muscle muscle) { return $"{muscle.id}%{muscle.intensity}"; } } internal static class SensationsBuilder { public static string From(Sensation sensation) { if (sensation is MicroSensation microsensation) { return From(microsensation); } if (sensation is SensationWithMuscles sensation2) { return From(sensation2); } if (sensation is SensationsSequence sequence) { return From(sequence); } return BakedSensationsBuilder.From(sensation as BakedSensation); } private static string From(SensationsSequence sequence) { string text = From(sequence.sensations[0]); for (int i = 1; i < sequence.sensations.Count; i++) { text = text + "&" + From(sequence.sensations[i]); } return text; } private static string From(SensationWithMuscles sensation) { return From(sensation.reference) + "|" + sensation.muscles.Stringify(); } private static string From(MicroSensation microsensation) { return $"{microsensation.frequency},{(int)Math.Round(microsensation.duration * 10f)},{microsensation.intensity}," + $"{(int)Math.Round(microsensation.rampUp * 1000f)},{(int)Math.Round(microsensation.rampDown * 1000f)},{(int)Math.Round(microsensation.exitDelay * 10f)},{microsensation.name}"; } } public static class Conversions { public static Multiplier ToPercentage(this float howMuch) { return (int)(howMuch * 100f); } } public static class MusclesExtensions { public static Muscle[] WithIntensity(this Muscle[] muscles, int intensity) { return muscles.Select((Muscle m) => m.WithIntensity(intensity)).ToArray(); } public static Muscle Mirror(this Muscle of) { return new Muscle(MirrorOf(of.id), of.intensity); } public static Muscle MultiplyIntensityBy(this Muscle of, Multiplier howMuch) { return new Muscle(of.id, howMuch * of.intensity); } public static Muscle[] MultiplyIntensityBy(this Muscle[] of, Multiplier howMuch) { return of.Select((Muscle muscle) => muscle.MultiplyIntensityBy(howMuch)).ToArray(); } public static Muscle[] Mirror(this Muscle[] of) { return of.Select(Mirror).ToArray(); } private static int MirrorOf(int aPosition) { return (aPosition % 2 == 0) ? (aPosition + 1) : (aPosition - 1); } public static string Stringify(this Muscle muscle) { return MusclesBuilder.From(muscle); } public static string Stringify(this Muscle[] muscles) { return MusclesBuilder.From(muscles); } } public static class SensationExtensions { public static Sensation WithPriority(this Sensation source, int priority) { source.Priority = priority; return source; } public static Sensation Append(this Sensation source, Sensation addend) { return new SensationsSequence(source, addend).WithPriority(source.Priority); } public static BakedSensation Bake(this Sensation source, int id, string name) { if (source is BakedSensation result) { return result; } return new BakedSensation(id, name, source, Icon.Empty, Family.None).WithPriority(source.Priority) as BakedSensation; } public static Sensation WithMuscles(this Sensation source, params Muscle[] muscles) { if (muscles.Length == 0 || source is SensationWithMuscles) { return source; } if (source is SensationsSequence sensationsSequence) { return new SensationsSequence(sensationsSequence.sensations.Select((Sensation s) => s.WithMuscles(muscles)).ToArray()).WithPriority(source.Priority); } return new SensationWithMuscles(source, muscles).WithPriority(source.Priority); } } public struct Family { private readonly string name; public static Family None { get; } = ""; private Family(string name) { this.name = name; } public static implicit operator string(Family family) { return family.name; } public static implicit operator Family(string name) { return new Family(name); } public override string ToString() { return name; } } public class GameAuth { public readonly string id; public readonly BakedSensation[] sensations = new BakedSensation[0]; public static GameAuth Empty => Create().WithId("0"); internal GameAuth() { } internal GameAuth(string id, params BakedSensation[] sensations) { this.id = id; this.sensations = sensations; } public GameAuth WithId(string id) { return new GameAuth(id, sensations); } public static GameAuth Parse(string auth) { return auth; } public static GameAuth Create(params BakedSensation[] sensations) { return new GameAuth("0", sensations); } public override string ToString() { return this; } public static implicit operator GameAuth(string auth) { return GamesParser.From(auth); } public static implicit operator string(GameAuth auth) { return GamesBuilder.Build(auth); } } public struct Icon { [StructLayout(LayoutKind.Sequential, Size = 1)] public struct Impact { private const string PREFIX = "Impact-"; public static Icon Ball => new Icon("Impact-" + 0); public static Icon Dart => new Icon("Impact-" + 1); public static Icon Punch => new Icon("Impact-" + 2); public static Icon Bullet => new Icon("Impact-" + 3); } [StructLayout(LayoutKind.Sequential, Size = 1)] public struct Weapon { private const string PREFIX = "Weapon-"; public static Icon Axe => new Icon("Weapon-" + 0); public static Icon Dagger => new Icon("Weapon-" + 1); public static Icon Gun => new Icon("Weapon-" + 2); public static Icon SubMachineGun => new Icon("Weapon-" + 3); } private readonly string name; public static Icon Empty => new Icon("0"); public static Icon Death => new Icon("Death-0"); public static Icon Spiders => new Icon("Spider-0"); public static Icon Weight => new Icon("Weight-0"); public static Icon Environment => new Icon("Environment-0"); public static Icon Alert => new Icon("Alert-0"); public static Icon Victory => new Icon("Victory-0"); internal Icon(string name) { this.name = name; } public static implicit operator Icon(string message) { return new Icon(message); } public static implicit operator string(Icon icon) { return icon.name; } public override string ToString() { return this; } } internal static class Math { public static T Clamp<T>(T val, T min, T max) where T : IComparable<T> { if (val.CompareTo(min) < 0) { return min; } if (val.CompareTo(max) > 0) { return max; } return val; } public static float Round(float value, int decimals = 1) { return (float)System.Math.Round(value, decimals); } } public class MicroSensation : Sensation { public readonly int frequency; public readonly float duration; public readonly int intensity; public readonly float rampUp; public readonly float rampDown; public readonly float exitDelay; public readonly string name; public override float Duration => duration + exitDelay; internal MicroSensation(int frequency, float duration, int intensity, float rampUp, float rampDown, float exitDelay, string name = "") { this.frequency = Math.Clamp(frequency, 1, 100); this.duration = Math.Round(Math.Clamp(duration, 0.1f, 20f)); this.intensity = Math.Clamp(intensity, 0, 100); this.rampUp = Math.Round(Math.Clamp(rampUp, 0f, 2f)); this.rampDown = Math.Round(Math.Clamp(rampDown, 0f, 2f)); this.exitDelay = Math.Round(Math.Clamp(exitDelay, 0f, 20f)); this.name = name; } public override Sensation MultiplyIntensityBy(Multiplier howMuch) { return new MicroSensation(frequency, duration, intensity * howMuch, rampUp, rampDown, exitDelay, name); } public MicroSensation WithName(string name) { return new MicroSensation(frequency, duration, intensity, rampUp, rampDown, exitDelay, name).WithPriority(base.Priority) as MicroSensation; } } public struct Multiplier { public readonly int value; private Multiplier(int value) { this.value = Math.Clamp(value, 0, value); } public static Multiplier operator *(Multiplier theFirst, int theSecond) { return new Multiplier(theFirst.value * theSecond / 100); } public static implicit operator Multiplier(int howmuch) { return new Multiplier(howmuch); } public static implicit operator int(Multiplier howmuch) { return howmuch.value; } } public readonly struct Muscle { public readonly int id; public readonly int intensity; public static Muscle Pectoral_R = new Muscle(0); public static Muscle Pectoral_L = new Muscle(1); public static Muscle Abdominal_R = new Muscle(2); public static Muscle Abdominal_L = new Muscle(3); public static Muscle Arm_R = new Muscle(4); public static Muscle Arm_L = new Muscle(5); public static Muscle Dorsal_R = new Muscle(6); public static Muscle Dorsal_L = new Muscle(7); public static Muscle Lumbar_R = new Muscle(8); public static Muscle Lumbar_L = new Muscle(9); public static Muscle[] All => Front.Concat(Back).ToArray(); public static Muscle[] Front => new Muscle[6] { Pectoral_R, Pectoral_L, Abdominal_R, Abdominal_L, Arm_R, Arm_L }; public static Muscle[] Back => new Muscle[4] { Dorsal_R, Dorsal_L, Lumbar_R, Lumbar_L }; internal Muscle(int id, int intensity = 100) { this.id = id; this.intensity = Math.Clamp(intensity, 0, 100); } public Muscle WithIntensity(int intensity) { return new Muscle(id, intensity); } public static Muscle[] Parse(string muscles) { return MusclesParser.Parse(muscles); } } internal static class BakedSensationsParser { private const char SEPARATOR = '~'; public static bool CanParse(string message) { return (!Enumerable.Contains(message, ',') && !Enumerable.Contains(message, '|')) || Enumerable.Contains(message, '~'); } public static BakedSensation From(string message) { if (!Enumerable.Contains(message, '~')) { return SensationsFactory.Create().Bake(int.Parse(message), ""); } string[] array = message.Split(new char[1] { '~' }); return new BakedSensation(int.Parse(array[0]), array[1], array[2], array[3], FamilyFrom(array)); } private static Family FamilyFrom(string[] parameters) { return (parameters.Length < 5) ? Family.None : ((Family)parameters[4]); } } internal static class GamesParser { public static GameAuth From(string auth) { string[] array = auth.Split(new char[1] { '#' }); if (int.TryParse(auth, out var _)) { return new GameAuth().WithId(auth); } if (string.IsNullOrEmpty(array[0])) { return new GameAuth(); } return new GameAuth("0", array.Select((string s) => BakedSensationsParser.From(s)).ToArray()); } } internal static class MicrosensationsParser { public const char SEPARATOR = ','; public static MicroSensation From(string message) { string[] array = message.Split(new char[1] { ',' }); return new MicroSensation(int.Parse(array[0]), float.Parse(array[1]) / 10f, int.Parse(array[2]), float.Parse(array[3]) / 1000f, float.Parse(array[4]) / 1000f, float.Parse(array[5]) / 10f, NameFrom(array)); } private static string NameFrom(string[] parameters) { return (parameters.Length >= 7) ? parameters[6] : ""; } } internal static class MusclesParser { public static Muscle[] Parse(string message) { string[] source = message.Split(new char[1] { ',' }); return source.Select((string m) => ParseSingle(m)).ToArray(); } public static Muscle ParseSingle(string message) { string[] array = message.Split(new char[1] { '%' }); return new Muscle(int.Parse(array[0]), int.Parse(array[1])); } } internal static class SensationsParser { public static Sensation From(string message) { if (BakedSensationsParser.CanParse(message)) { return BakedSensationsParser.From(message); } if (SequenceParser.CanParse(message)) { return SequenceParser.From(message); } if (SensationWithMusclesParser.CanParse(message)) { return SensationWithMusclesParser.From(message); } return MicrosensationsParser.From(message); } } internal static class SensationWithMusclesParser { public const char SEPARATOR = '|'; public static bool CanParse(string message) { return Enumerable.Contains(message, '|'); } public static SensationWithMuscles From(string message) { string[] array = message.Split(new char[1] { '|' }); return new SensationWithMuscles(array[0], MusclesParser.Parse(array[1])); } } internal static class SequenceParser { public const char SEPARATOR = '&'; public static bool CanParse(string message) { return Enumerable.Contains(message, '&'); } public static Sensation From(string message) { string[] source = message.Split(new char[1] { '&' }); Sensation[] sensations = ((IEnumerable<string>)source).Select((Func<string, Sensation>)((string s) => s)).ToArray(); return new SensationsSequence(sensations); } } public abstract class Sensation { public int Priority { get; set; } = 0; public abstract float Duration { get; } public static Sensation Ball => SensationsFactory.Create(); public static Sensation Dart => SensationsFactory.Create(10); public static Sensation Dagger => DaggerEntry.Append(DaggerMovement); public static Sensation DaggerEntry => SensationsFactory.Create(60, 0.2f); public static Sensation DaggerMovement => SensationsFactory.Create(100, 2f, 100, 0.3f, 0.1f); public static Sensation ShotWithExit => ShotEntry.Append(ShotExit).Append(ShotBleeding); public static Sensation ShotEntry => SensationsFactory.Create(30).WithMuscles(Muscle.Pectoral_R); public static Sensation ShotExit => SensationsFactory.Create(20).WithMuscles(Muscle.Dorsal_R); public static Sensation ShotBleeding => SensationsFactory.Create(50, 0.5f, 80, 0f, 0.3f).WithMuscles(Muscle.Pectoral_R, Muscle.Pectoral_L); public static Sensation Parse(string message) { return message; } public static implicit operator Sensation(string message) { return SensationsParser.From(message); } public static implicit operator string(Sensation sensation) { return SensationsBuilder.From(sensation); } public override string ToString() { return this; } public abstract Sensation MultiplyIntensityBy(Multiplier howMuch); } public static class SensationsFactory { public static MicroSensation Create(int frequency = 100, float durationSeconds = 0.1f, int intensityPercentage = 100, float rampUpMillis = 0f, float rampDownMillis = 0f, float exitDelaySeconds = 0f) { return new MicroSensation(frequency, durationSeconds, intensityPercentage, rampUpMillis, rampDownMillis, exitDelaySeconds); } } public class SensationsSequence : Sensation { public readonly List<Sensation> sensations; public override float Duration => sensations.Sum((Sensation s) => s.Duration); public SensationsSequence(params Sensation[] sensations) { this.sensations = new List<Sensation>(sensations); } public override Sensation MultiplyIntensityBy(Multiplier howMuch) { return new SensationsSequence(sensations.Select((Sensation s) => s.MultiplyIntensityBy(howMuch)).ToArray()); } } public class SensationWithMuscles : Sensation { public readonly Sensation reference; public readonly Muscle[] muscles; public override float Duration => reference.Duration; public SensationWithMuscles(Sensation reference, Muscle[] muscles) { this.reference = reference; this.muscles = muscles; } public override Sensation MultiplyIntensityBy(Multiplier howMuch) { return new SensationWithMuscles(reference, muscles.MultiplyIntensityBy(howMuch)); } } public enum ConnectionState { Connected, Disconnected, Connecting } public interface Network { List<Address> ConnectedServers { get; } ConnectionState State { get; set; } bool IsConnecting { get; } bool IsConnected { get; } void SendTo(string message, string addressee); string Listen(out Address sender); void Connect(Address server); void Disconnect(); void Close(); void PortTo(int newPort); } public class Client { private readonly Network network; private readonly SendMessage sendMessage; private readonly FindServer findServer; private readonly ListenForDisconnection disconnection; private readonly CandidatesVault candidates; public ConnectionState State => network.State; public bool IsConnected => network.ConnectedServers != null && network.ConnectedServers.Count != 0; private bool CanScan => network.State == ConnectionState.Disconnected; public List<string> DiscoveredServers => candidates.StoredServers; internal Client(Network network, SendMessage sendMessage, FindServer findServer, ListenForDisconnection disconnection, CandidatesVault keys) { this.network = network; this.sendMessage = sendMessage; this.findServer = findServer; this.disconnection = disconnection; candidates = keys; } ~Client() { Close(); } internal Task ScanServer() { if (!CanScan) { return Task.CompletedTask; } candidates.Clean(); return findServer.Scan(); } internal Task FindServer(string auth, params string[] addresses) { if (!CanScan) { return Task.CompletedTask; } if (addresses[0] == "255.255.255.255") { candidates.Clean(); } if (addresses.Length == 1) { return FindServer(addresses[0], auth); } return Task.Run(() => findServer.Execute(addresses, auth)); } private async Task FindServer(string address, string auth) { await findServer.ExecuteWithAbscense(address, auth); ListenForDisconnection(address, auth); } private async Task ListenForDisconnection(string addressee, string auth) { if (await disconnection.Listen()) { Disconnect(); await FindServer(addressee, auth); } } public void Send(string message) { network.ConnectedServers.ForEach(delegate(Address server) { sendMessage.Execute(message, server); }); } public void Disconnect() { network.Disconnect(); } public void Close() { network.Close(); } public void ChangeConnectionAttemptRate(int newRate) { findServer.DelayTime = newRate; disconnection.delayTime = newRate; } public void PortTo(int newPort) { network.PortTo(newPort); } } internal static class ClientFactory { public static Client Create(Network network, Message secretKey = default(Message), CandidatesVault keysVault = null, int scanDelayMs = 500) { if (keysVault == null) { keysVault = new CandidatesVault(); } if (secretKey.addressee != null) { keysVault.Store(secretKey); } SendMessage sendMessage = new SendMessage(network); NotifyAbscense notifyAbscense = new NotifyAbscense(network, keysVault); SendAuthMessage sendAuth = new SendAuthMessage(sendMessage, keysVault); ReceiveAvailableApp receiveSecretKey = new ReceiveAvailableApp(keysVault, network); FindServer findServer = new FindServer(network, notifyAbscense, receiveSecretKey, sendAuth, keysVault) { DelayTime = scanDelayMs }; ListenForDisconnection disconnection = new ListenForDisconnection(network); return new Client(network, sendMessage, findServer, disconnection, keysVault); } } internal class FindServer { private readonly Network network; private readonly NotifyAbscense notifyAbscense; private readonly ReceiveAvailableApp interpretAppMessage; private readonly SendAuthMessage sendAuth; private readonly CandidatesVault candidates; public int DelayTime = 500; private Task TimeBetweenAttempts => Task.Delay(DelayTime); public FindServer(Network network, NotifyAbscense notifyAbscense, ReceiveAvailableApp receiveSecretKey, SendAuthMessage sendAuth, CandidatesVault keys) { this.network = network; this.notifyAbscense = notifyAbscense; interpretAppMessage = receiveSecretKey; this.sendAuth = sendAuth; candidates = keys; } public async Task ExecuteWithAbscense(Address addressee, string auth) { await Execute(new string[1] { addressee }, auth); notifyAbscense.Execute(auth); } public async Task Execute(string[] addressees, string auth) { network.State = ConnectionState.Connecting; foreach (string address in addressees) { if (candidates.ContainsCandidate(address)) { sendAuth.Execute(auth, address); } } do { Address sender; string lastMessage = ReceiveMessage(out sender); if (string.IsNullOrEmpty(lastMessage)) { foreach (string address2 in addressees) { NotifyPresence(address2); } } if (lastMessage.Equals("okay")) { candidates.Store(new Message("", sender)); sendAuth.Execute(auth, sender); } else if (IsConnectionVerification(addressees, sender, lastMessage)) { network.Connect(sender); } await TimeBetweenAttempts; sender = default(Address); } while (network.ConnectedServers.Count != addressees.Count() && network.State != ConnectionState.Disconnected); } private bool IsConnectionVerification(string[] addresse, string sender, string lastMessage) { return lastMessage.Equals("pong") && (addresse.Contains(sender) || addresse[0] == "255.255.255.255"); } private string ReceiveMessage(out Address sender) { return network.Listen(out sender); } public async Task Scan() { while (network.State == ConnectionState.Disconnected) { NotifyPresence("255.255.255.255"); await interpretAppMessage.Execute(); await TimeBetweenAttempts; } } private void NotifyPresence(string addressee) { network.SendTo("ping", addressee); } } internal class ListenForDisconnection { private readonly Network network; public int delayTime = 50; private Task ListenDelay => Task.Delay(delayTime); public ListenForDisconnection(Network network) { this.network = network; } public async Task<bool> Listen() { while (network.State == ConnectionState.Connected) { Address sender; string message = network.Listen(out sender); if (message.Equals("OWO_Close") && network.ConnectedServers.Contains(sender)) { return true; } await ListenDelay; sender = default(Address); } return false; } } internal class NotifyAbscense { private readonly Network network; private readonly CandidatesVault candidates; public NotifyAbscense(Network network, CandidatesVault candidates) { this.network = network; this.candidates = candidates; } public void Execute(string authCommand) { string text = authCommand.Split(new char[1] { '*' })[0]; foreach (string storedServer in candidates.StoredServers) { string message = text + "*GAMEUNAVAILABLE"; network.SendTo(message, storedServer); } } } internal class ReceiveAvailableApp { private readonly Network network; private readonly CandidatesVault keys; public ReceiveAvailableApp(CandidatesVault secretKeys, Network network) { keys = secretKeys; this.network = network; } public Task Execute() { Address sender; string text = network.Listen(out sender); if (!text.Equals("okay")) { return Task.CompletedTask; } keys.Store(new Message("", sender)); return Task.CompletedTask; } } internal class SendAuthMessage { private readonly SendMessage send; private readonly CandidatesVault keys; public SendAuthMessage(SendMessage send, CandidatesVault keys) { this.send = send; this.keys = keys; } public void Execute(string auth, Address addressee) { if (addressee.Equals(Address.Any)) { foreach (string storedServer in keys.StoredServers) { send.Execute(auth, storedServer); } return; } if (keys.ContainsCandidate(addressee)) { send.Execute(auth, addressee); } } } internal class SendMessage { private readonly Network network; public SendMessage(Network network) { this.network = network; } public void Execute(string message, Address addressee) { if (addressee.IsValid) { network.SendTo(message, addressee); } } } internal class ASCIIEncoder { public string Decode(byte[] buffer, int messageLength) { return Encoding.ASCII.GetString(buffer, 0, messageLength); } public byte[] Encode(string message) { return Encoding.ASCII.GetBytes(message); } } internal class UDPNetwork : Network { private readonly byte[] buffer; private readonly Socket socket; private readonly ASCIIEncoder encoding; private int PORT = 54020; public List<Address> ConnectedServers { get; private set; } = new List<Address>(); public ConnectionState State { get; set; } = ConnectionState.Disconnected; public bool IsConnecting => State == ConnectionState.Connecting; public bool IsConnected => State == ConnectionState.Connected; public UDPNetwork() { buffer = new byte[1024]; socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.EnableBroadcast = true; socket.ReceiveTimeout = 2500; socket.Blocking = false; encoding = new ASCIIEncoder(); } public string Listen(out Address address) { try { EndPoint remoteEP = new IPEndPoint(0L, 0); string result = encoding.Decode(buffer, socket.ReceiveFrom(buffer, ref remoteEP)); address = new Address((remoteEP as IPEndPoint).Address.ToString()); return result; } catch { address = Address.Empty; return string.Empty; } } public void SendTo(string message, string addressee) { socket.SendTo(encoding.Encode(message), new IPEndPoint(IPAddress.Parse(addressee), PORT)); } public void Connect(Address address) { if (!ConnectedServers.Contains(address)) { ConnectedServers.Add(address); } State = ConnectionState.Connected; } public void Disconnect() { ConnectedServers.Clear(); State = ConnectionState.Disconnected; } public void Close() { socket.Close(); } public void PortTo(int newPort) { PORT = newPort; } } public struct Address { public readonly string value; public bool IsValid => !string.IsNullOrEmpty(value); public static Address Any => new Address("255.255.255.255"); public static Address Empty => new Address(string.Empty); public static Address Null => new Address(null); public Address(string value) { this.value = value; } public static implicit operator string(Address addressee) { return addressee.value; } public static implicit operator Address(string value) { return new Address(value); } public static Address Create(string ip) { return new Address(ip); } } internal class CandidatesVault { private HashSet<Address> candidateServers = new HashSet<Address>(); public Address LastApp => candidateServers.FirstOrDefault(); public List<string> StoredServers => candidateServers.Select((Address candidate) => candidate.value).ToList(); public void Store(Message message) { if (message.HasAddressee) { candidateServers.Add(message.addressee); } } public void Clean() { candidateServers.Clear(); } public bool ContainsCandidate(Address address) { return candidateServers.Contains(address); } } internal struct Message { public readonly string value; public readonly string addressee; public bool IsEmpty => string.IsNullOrEmpty(value); public bool HasAddressee => !string.IsNullOrEmpty(addressee); public static Message Invalid => new Message(string.Empty, Address.Empty); public Message(string value, string addresseeIP) { this.value = value; addressee = addresseeIP; } } } namespace OWOGame.Infraestructure { public class RealTimeClock { private readonly Stopwatch stopwatch; public long TotalMilliseconds => (long)stopwatch.Elapsed.TotalMilliseconds; public RealTimeClock() { stopwatch = new Stopwatch(); stopwatch.Start(); } } } namespace OWOGame.Controller { internal class Connect { private GameAuth game = GameAuth.Empty; private readonly Client client; public Connect(Client client) { this.client = client; } public Task ScanServer() { return client.ScanServer(); } public Task AutoConnect() { return client.FindServer($"{game.id}*AUTH*{game}", "255.255.255.255"); } public Task ManualConnect(params string[] ips) { return client.FindServer($"{game.id}*AUTH*{game}", ips); } public void Configure(GameAuth game) { this.game = game; } } internal class Disconnect { private readonly Client client; public Disconnect(Client client) { this.client = client; } public void Execute() { if (client.State != ConnectionState.Disconnected) { client.Disconnect(); } } } internal class SendMessage { private readonly Client client; public SendMessage(Client client) { this.client = client; } public void Execute(string message) { if (client.IsConnected) { client.Send(message); } } } internal class SendSensation : SendMessage { private long whenLastSensationEnds; private int lastPriority = -1; private GameAuth game = GameAuth.Empty; public SendSensation(Client network) : base(network) { } public void Execute(Sensation sensation, long currentTimeMs) { if (lastPriority <= sensation.Priority || currentTimeMs >= whenLastSensationEnds) { Execute($"{game.id}*SENSATION*{sensation}"); whenLastSensationEnds = currentTimeMs + (int)(sensation.Duration * 1000f); lastPriority = sensation.Priority; } } public void Configure(GameAuth game) { this.game = game; } public void ResetPriority() { whenLastSensationEnds = 0L; } } internal class StopSensation : SendMessage { private GameAuth game = GameAuth.Empty; public StopSensation(Client network) : base(network) { } public void Execute() { Execute(game.id + "*STOP"); } public void Configure(GameAuth game) { this.game = game; } } }