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 WalkieSpamExplode v1.0.3
WalkieSpamExplode.dll
Decompiled 20 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CSync.Extensions; using CSync.Lib; using GameNetcodeStuff; using HarmonyLib; using LethalNetworkAPI; using Unity.Netcode; 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("ConfigurableJesterFear")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ConfigurableJesterFear")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("003065a8-0692-4bbd-94f6-39874df19d47")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace WalkieSpamExplode; public class ConfigHandler : SyncedConfig2<ConfigHandler> { public ConfigEntry<bool> debugMode; [SyncedEntryField] public SyncedEntry<bool> punishWalkieSpam; [SyncedEntryField] public SyncedEntry<bool> punishRemoteSpam; [SyncedEntryField] public SyncedEntry<float> walkieExplosionChance; [SyncedEntryField] public SyncedEntry<float> remoteExplosionChance; [SyncedEntryField] public SyncedEntry<bool> destroyItem; [SyncedEntryField] public SyncedEntry<bool> explosionInOrbit; [SyncedEntryField] public SyncedEntry<int> maxAnger; [SyncedEntryField] public SyncedEntry<int> walkieAngerIncreaseAmount; [SyncedEntryField] public SyncedEntry<int> remoteAngerIncreaseAmount; [SyncedEntryField] public SyncedEntry<float> angerDecreaseInterval; [SyncedEntryField] public SyncedEntry<float> angerDecreaseAmount; [SyncedEntryField] public SyncedEntry<int> angerWarningThreshold; [SyncedEntryField] public SyncedEntry<float> explosionRadius; [SyncedEntryField] public SyncedEntry<float> damageRadius; [SyncedEntryField] public SyncedEntry<int> nonLethalDamage; public ConfigHandler(ConfigFile config) : base("ZetaArcade.WalkieSpamExplode") { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Expected O, but got Unknown debugMode = config.Bind<bool>("Debugging", "Print Debug Info", false, "If true, prints debug info into the log."); punishWalkieSpam = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "General", "Punish Walkie Spam", true, "If true, the player will be punished for spamming the Walkie."); punishRemoteSpam = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "General", "Punish Remote Spam", true, "If true, the player will be punished for spamming the Remote."); destroyItem = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "General", "Destroy Item", true, "If true, when the max anger is reached and the player is punished, the item (Walkie/Remote) should also be destroyed. Disable if you don't want the item destroyed. Note that since the Remote doesn't have a battery, if the explosion fails it will always be destroyed, regardless of this config."); maxAnger = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Anger", "Max Anger", 100, new ConfigDescription("The maximum amount of anger that is stored. Anger is added everytime they press the use key on the walkie, or toggle the lights with the Remote. When this value is reached, the player spamming the item will be punished.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>())); walkieAngerIncreaseAmount = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Anger", "Walkie Anger Increase Amount", 25, "How much anger is added to the 'anger meter' everytime they turn a Walkie on."); remoteAngerIncreaseAmount = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Anger", "Remote Anger Increase Amount", 20, "How much anger is added to the 'anger meter' everytime they toggle the ship lights with the Remote."); angerDecreaseInterval = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Anger", "Anger Decrease Interval", 1f, "How often the anger meter decreases by the anger decrease rate, in seconds."); angerDecreaseAmount = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Anger", "Anger Decrease Amount", 5f, "How much anger is decreased every anger decrease interval, stopping at 0. Recommended to be above 0, or else Anger effectively never decreases."); angerWarningThreshold = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Anger", "Anger Warning Threshold", 70, "When the anger reaches this amount, the player will be given a warning message in the HUD. Set to 0 or above the Max Anger to disable."); walkieExplosionChance = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Explosion", "Walkie Explosion Chance", 100f, new ConfigDescription("The chance that the walkie will explode when max anger is reached, where 100 = 100%. If it fails, the battery will instead just be drained to 0. Set to 0 to disable explosions.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); remoteExplosionChance = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Explosion", "Remote Explosion Chance", 100f, new ConfigDescription("The chance that the remote will explode when max anger is reached, where 100 = 100%. If it fails, item will just be destroyed without the explosion. Set to 0 to disable explosions.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); explosionInOrbit = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Explosion", "Explosion in Orbit", false, "If true, explosions can occur when in orbit, when the ship is still landing etc."); explosionRadius = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Explosion", "Explosion Radius", 6.5f, "The radius of the explosion (instakill) "); damageRadius = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Explosion", "Damage Radius", 7.5f, "The outer radius of the explosion, where it damages the player but doesn't kill them"); nonLethalDamage = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Explosion", "Non-Lethal Damage", 50, new ConfigDescription("The amount of non-lethal damage caused by the outer radius of the explosion", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); ConfigManager.Register<ConfigHandler>((SyncedConfig2<ConfigHandler>)this); } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("ZetaArcade.WalkieSpamExplode", "WalkieSpamExplode", "1.0.3")] public class WalkieSpamExplodeBase : BaseUnityPlugin { [HarmonyPatch(typeof(WalkieTalkie))] internal class WalkieTalkiePatch { [HarmonyPatch("SendWalkieTalkieStartTransmissionSFX")] [HarmonyPostfix] private static void SwitchWalkieTalkieOnPatch(WalkieTalkie __instance, ref bool ___isBeingUsed) { if (!___isBeingUsed) { return; } PlayerControllerB playerHeldBy = ((GrabbableObject)__instance).playerHeldBy; if (!((Object)(object)playerHeldBy == (Object)null) && playerHeldBy.playerClientId == NetworkManager.Singleton.LocalClientId) { if (Config.debugMode.Value) { Logger.LogInfo((object)"Walkie triggered!"); } WalkieSpamNetwork.SendWalkieUsed(playerHeldBy.playerClientId, Config.walkieAngerIncreaseAmount.Value, isRemote: false); } } } [HarmonyPatch(typeof(RemoteProp))] internal class RemotePropPatch { [HarmonyPatch("ItemActivate")] [HarmonyPostfix] private static void ItemActivatePatch(RemoteProp __instance, bool used, bool buttonDown) { if (!used || !buttonDown) { return; } PlayerControllerB playerHeldBy = ((GrabbableObject)__instance).playerHeldBy; if (!((Object)(object)playerHeldBy == (Object)null) && playerHeldBy.playerClientId == NetworkManager.Singleton.LocalClientId) { if (Config.debugMode.Value) { Logger.LogInfo((object)"Remote triggered!"); } WalkieSpamNetwork.SendWalkieUsed(playerHeldBy.playerClientId, Config.remoteAngerIncreaseAmount.Value, isRemote: true); } } } [HarmonyPatch(typeof(StartOfRound))] internal class StartOfRoundPatch2 { [HarmonyPatch("ArriveAtLevel")] [HarmonyPostfix] private static void Arrive(StartOfRound __instance) { } } private Harmony harmony = new Harmony("ZetaArcade.WalkieSpamExplode"); public static ManualLogSource Logger; public static WalkieSpamExplodeBase Instance; private readonly Dictionary<ulong, float> angerLevels = new Dictionary<ulong, float>(); private Coroutine decreaseAngerCoroutine; internal static ConfigHandler Config; private static readonly MethodInfo BroadcastSFXMethod = typeof(WalkieTalkie).GetMethod("BroadcastSFXFromWalkieTalkie", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo PlayerDeathSFXField = typeof(WalkieTalkie).GetField("playerDieOnWalkieTalkieSFX", BindingFlags.Instance | BindingFlags.NonPublic); private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; } Logger = ((BaseUnityPlugin)this).Logger; Config = new ConfigHandler(((BaseUnityPlugin)this).Config); WalkieSpamNetwork.Init(); Logger.LogInfo((object)"Plugin is loaded!"); harmony.PatchAll(); } public static void BroadcastWalkieDeathSFX(WalkieTalkie walkie, ulong playerID) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown if (BroadcastSFXMethod == null) { Logger.LogError((object)"Could not find BroadcastSFXFromWalkieTalkie"); return; } if (PlayerDeathSFXField == null) { Logger.LogError((object)"Could not find playerDieOnWalkieTalkieSFX"); return; } AudioClip val = (AudioClip)PlayerDeathSFXField.GetValue(walkie); BroadcastSFXMethod.Invoke(walkie, new object[2] { val, (int)playerID }); } public void IncreaseAnger(ulong playerID, int amount, bool isRemote = false) { if (NetworkManager.Singleton.IsHost) { if (!angerLevels.ContainsKey(playerID)) { angerLevels[playerID] = 0f; } if (Config.debugMode.Value) { Logger.LogInfo((object)$"Player {playerID} anger before: {angerLevels[playerID]}"); } angerLevels[playerID] += amount; if (Config.debugMode.Value) { Logger.LogInfo((object)$"Player {playerID} anger after: {angerLevels[playerID]}"); } if (angerLevels[playerID] >= (float)Config.maxAnger.Value) { PunishPlayer(playerID, isRemote); } else if (angerLevels[playerID] >= (float)Config.angerWarningThreshold.Value && Config.angerWarningThreshold.Value > 0) { WarnPlayer(playerID); } if (decreaseAngerCoroutine == null) { decreaseAngerCoroutine = ((MonoBehaviour)this).StartCoroutine(DecreaseAngerOverTime()); } } } private IEnumerator DecreaseAngerOverTime() { while (true) { yield return (object)new WaitForSeconds(Config.angerDecreaseInterval.Value); List<ulong> playersToReset = new List<ulong>(); foreach (ulong player in angerLevels.Keys.ToList()) { angerLevels[player] -= Config.angerDecreaseAmount.Value; if (Config.debugMode.Value) { Logger.LogInfo((object)$"Player {player} anger decreased by {Config.angerDecreaseAmount.Value}. Current anger: {angerLevels[player]}"); } if (angerLevels[player] <= 0f) { playersToReset.Add(player); } } foreach (ulong player2 in playersToReset) { angerLevels.Remove(player2); } } } public void WarnPlayer(ulong playerID) { WalkieSpamNetwork.SendWarning(playerID); } public void ShowWarning() { HUDManager.Instance.DisplayTip("WARNING", "Spammers will be punished!", true, false, "LC_Tip1"); } public void PunishPlayer(ulong playerID, bool isRemote) { //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)StartOfRound.Instance == (Object)null) { return; } float num = Random.Range(0f, 100f); if (num <= Config.walkieExplosionChance.Value && !isRemote) { if ((Config.explosionInOrbit.Value || StartOfRound.Instance.inShipPhase || !StartOfRound.Instance.shipHasLanded || StartOfRound.Instance.shipIsLeaving) && !Config.explosionInOrbit.Value) { return; } PlayerControllerB val = ((IEnumerable<PlayerControllerB>)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB p) => p.playerClientId == playerID)); if ((Object)(object)val == (Object)null) { return; } WalkieTalkie val2 = null; GrabbableObject[] itemSlots = val.ItemSlots; foreach (GrabbableObject val3 in itemSlots) { if (!((Object)(object)val3 == (Object)null)) { WalkieTalkie val4 = (WalkieTalkie)(object)((val3 is WalkieTalkie) ? val3 : null); if (val4 != null) { val2 = val4; break; } } } if ((Object)(object)val2 != (Object)null) { BroadcastWalkieDeathSFX(val2, playerID); } WalkieSpamNetwork.SendExplosion(((Component)val).transform.position, playerID, isRemote); ResetAnger(playerID); } else if (num <= Config.remoteExplosionChance.Value && isRemote) { if ((Config.explosionInOrbit.Value || StartOfRound.Instance.inShipPhase || !StartOfRound.Instance.shipHasLanded || StartOfRound.Instance.shipIsLeaving) && !Config.explosionInOrbit.Value) { return; } PlayerControllerB val5 = ((IEnumerable<PlayerControllerB>)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB p) => p.playerClientId == playerID)); if ((Object)(object)val5 == (Object)null) { return; } RemoteProp val6 = null; GrabbableObject[] itemSlots2 = val5.ItemSlots; foreach (GrabbableObject val7 in itemSlots2) { if (!((Object)(object)val7 == (Object)null)) { RemoteProp val8 = (RemoteProp)(object)((val7 is RemoteProp) ? val7 : null); if (val8 != null) { val6 = val8; break; } } } if ((Object)(object)val6 != (Object)null) { } WalkieSpamNetwork.SendExplosion(((Component)val5).transform.position, playerID, isRemote); ResetAnger(playerID); } else if (isRemote) { WalkieSpamNetwork.SendDestroyWalkie(playerID, isRemote: true); } else { WalkieSpamNetwork.SendBatteryDrain(playerID); } } public void ResetAnger(ulong playerID) { angerLevels.Remove(playerID); } public void DrainBattery(ulong playerID) { if (!Object.op_Implicit((Object)(object)StartOfRound.Instance) || !Object.op_Implicit((Object)(object)StartOfRound.Instance.currentLevel)) { return; } PlayerControllerB localPlayerController = StartOfRound.Instance.localPlayerController; if (!Object.op_Implicit((Object)(object)localPlayerController) || localPlayerController.playerClientId != playerID) { return; } GrabbableObject[] itemSlots = localPlayerController.ItemSlots; foreach (GrabbableObject val in itemSlots) { if (!((Object)(object)val == (Object)null) && val.insertedBattery != null && !val.insertedBattery.empty && !(val.itemProperties.itemName != "Walkie-talkie")) { val.insertedBattery.charge = 0f; if (Config.destroyItem.Value) { WalkieSpamNetwork.SendDestroyWalkie(playerID, isRemote: false); } } } } public void DestroyWalkie(ulong playerID, bool isRemote) { PlayerControllerB val = ((IEnumerable<PlayerControllerB>)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB p) => p.playerClientId == playerID)); if ((Object)(object)val == (Object)null) { return; } GrabbableObject[] itemSlots = val.ItemSlots; foreach (GrabbableObject val2 in itemSlots) { if (!((Object)(object)val2 == (Object)null)) { string text = (isRemote ? "Remote" : "Walkie-talkie"); if (!(val2.itemProperties.itemName != text)) { val.carryWeight = Mathf.Clamp(val.carryWeight - (val2.itemProperties.weight - 1f), 1f, 10f); val.DestroyItemInSlot(Array.IndexOf(val.ItemSlots, val2)); break; } } } } public static void ReceiveSelfDestruct(Vector3 position, ulong clientId, bool isRemote) { //IL_00b3: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)StartOfRound.Instance) && Object.op_Implicit((Object)(object)StartOfRound.Instance.currentLevel)) { PlayerControllerB localPlayerController = StartOfRound.Instance.localPlayerController; if (Config.destroyItem.Value) { WalkieSpamNetwork.SendDestroyWalkie(clientId, isRemote); } if (Object.op_Implicit((Object)(object)localPlayerController) && localPlayerController.playerClientId == clientId) { Vector3 val = new Vector3(Random.Range(-1f, 1f), 1f, Random.Range(-1f, 1f)); Vector3 val2 = ((Vector3)(ref val)).normalized * 30f; } Landmine.SpawnExplosion(position, true, Config.explosionRadius.Value, Config.damageRadius.Value, Config.nonLethalDamage.Value, 0f, (GameObject)null, false); } } } internal class PluginInfo { public const string modGUID = "ZetaArcade.WalkieSpamExplode"; public const string modName = "WalkieSpamExplode"; public const string modVersion = "1.0.3"; } public static class WalkieSpamNetwork { public static LNetworkMessage<WalkieMessage> WalkieUsedMessage; public static LNetworkMessage<WarningMessage> WarningMessage; public static LNetworkMessage<ExplosionMessage> ExplosionMessage; public static LNetworkMessage<DestroyWalkieMessage> DestroyWalkieMessage; public static LNetworkMessage<BatteryDrainMessage> BatteryDrainMessage; public static void Init() { WalkieUsedMessage = LNetworkMessage<WalkieMessage>.Create("WalkieUsed", (Action<WalkieMessage, ulong>)OnWalkieUsed, (Action<WalkieMessage>)null, (Action<WalkieMessage, ulong>)null); WarningMessage = LNetworkMessage<WalkieSpamExplode.WarningMessage>.Create("Warning", (Action<WarningMessage, ulong>)null, (Action<WarningMessage>)OnWarningReceived, (Action<WarningMessage, ulong>)null); ExplosionMessage = LNetworkMessage<WalkieSpamExplode.ExplosionMessage>.Create("Explosion", (Action<ExplosionMessage, ulong>)null, (Action<ExplosionMessage>)OnExplosionReceived, (Action<ExplosionMessage, ulong>)null); DestroyWalkieMessage = LNetworkMessage<WalkieSpamExplode.DestroyWalkieMessage>.Create("DestroyWalkie", (Action<DestroyWalkieMessage, ulong>)OnDestroyWalkieReceived, (Action<DestroyWalkieMessage>)null, (Action<DestroyWalkieMessage, ulong>)null); BatteryDrainMessage = LNetworkMessage<WalkieSpamExplode.BatteryDrainMessage>.Create("BatteryDrain", (Action<BatteryDrainMessage, ulong>)null, (Action<BatteryDrainMessage>)OnBatteryDrainReceived, (Action<BatteryDrainMessage, ulong>)null); } private static void OnWalkieUsed(WalkieMessage message, ulong senderClientId) { if (NetworkManager.Singleton.IsHost && !((Object)(object)WalkieSpamExplodeBase.Instance == (Object)null)) { WalkieSpamExplodeBase.Instance.IncreaseAnger(message.playerID, message.amount, message.isRemote); } } private static void OnWarningReceived(WarningMessage message) { if (!((Object)(object)StartOfRound.Instance == (Object)null) && !((Object)(object)StartOfRound.Instance.localPlayerController == (Object)null) && StartOfRound.Instance.localPlayerController.playerClientId == message.playerID) { WalkieSpamExplodeBase.Instance.ShowWarning(); } } private static void OnExplosionReceived(ExplosionMessage message) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) WalkieSpamExplodeBase.ReceiveSelfDestruct(new Vector3(message.x, message.y, message.z), message.playerID, message.isRemote); } private static void OnDestroyWalkieReceived(DestroyWalkieMessage message, ulong senderClientId) { if (NetworkManager.Singleton.IsHost) { WalkieSpamExplodeBase.Instance.DestroyWalkie(message.playerID, message.isRemote); } } private static void OnBatteryDrainReceived(BatteryDrainMessage message) { if (!((Object)(object)StartOfRound.Instance == (Object)null) && !((Object)(object)StartOfRound.Instance.localPlayerController == (Object)null) && StartOfRound.Instance.localPlayerController.playerClientId == message.playerID) { WalkieSpamExplodeBase.Instance.DrainBattery(message.playerID); } } public static void SendWalkieUsed(ulong playerID, int amount, bool isRemote) { if (NetworkManager.Singleton.IsListening) { WalkieUsedMessage.SendServer(new WalkieMessage { playerID = playerID, amount = amount, isRemote = isRemote }); } } public static void SendWarning(ulong playerID) { if (NetworkManager.Singleton.IsHost) { WarningMessage.SendClient(new WarningMessage { playerID = playerID }, playerID); } } public static void SendExplosion(Vector3 position, ulong playerID, bool isRemote) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (NetworkManager.Singleton.IsHost) { ExplosionMessage.SendClients(new ExplosionMessage { x = position.x, y = position.y, z = position.z, playerID = playerID, isRemote = isRemote }, NetworkManager.Singleton.ConnectedClientsIds.ToArray()); } } public static void SendDestroyWalkie(ulong playerID, bool isRemote) { DestroyWalkieMessage.SendServer(new DestroyWalkieMessage { playerID = playerID, isRemote = isRemote }); } public static void SendBatteryDrain(ulong playerID) { if (NetworkManager.Singleton.IsHost) { BatteryDrainMessage.SendClient(new BatteryDrainMessage { playerID = playerID }, playerID); } } } public class ExplosionMessage { public float x; public float y; public float z; public ulong playerID; public bool isRemote; } public class WalkieMessage { public ulong playerID; public int amount; public bool isRemote; } public class WarningMessage { public ulong playerID; } public class BatteryDrainMessage { public ulong playerID; } public class DestroyWalkieMessage { public ulong playerID; public bool isRemote; }