using System; using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.InnerNet.GameDataMessages; using EHR.Gamemodes; using EHR.Modules; using EHR.Roles; using HarmonyLib; using Hazel; using InnerNet; using UnityEngine; using Tree = EHR.Roles.Tree; // Credit: https://github.com/Rabek009/MoreGamemodes/blob/e054eb498094dfca0a365fc6b6fea8d17f9974d7/Modules/AllObjects // Huge thanks to Rabek009 for this code! namespace EHR { public class CustomNetObject { public static readonly List AllObjects = []; private static int MaxId = -1; protected int Id; public PlayerControl playerControl; public Vector2 Position; protected string Sprite; public void RpcChangeSprite(string sprite) { if (!AmongUsClient.Instance.AmHost) return; if (this is not DisasterWarningTimer) Logger.Info($" Change Custom Net Object {GetType().Name} (ID {Id}) sprite", "CNO.RpcChangeSprite"); Sprite = sprite; string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; var sender = CustomRpcSender.Create("CustomNetObject.RpcChangeSprite", this is BedWarsItemGenerator ? SendOption.None : SendOption.Reliable, log: false); MessageWriter writer = sender.stream; sender.StartMessage(); PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; writer.StartMessage(1); { writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); PlayerControl.LocalPlayer.Data.Serialize(writer, false); } writer.EndMessage(); try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } catch (Exception e) { Utils.ThrowException(e); } sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) .WriteNetObject(PlayerControl.LocalPlayer) .Write(false) .EndRpc(); PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; writer.StartMessage(1); { writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); PlayerControl.LocalPlayer.Data.Serialize(writer, false); } writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); playerControl.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(true); LateTask.New(() => Utils.SendRPC(CustomRPC.FixModdedClientCNO, playerControl, true), 0.3f); } public void TP(Vector2 position) { Position = position; } public void Despawn() { if (!AmongUsClient.Instance.AmHost) return; Logger.Info($" Despawn Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn"); try { if (playerControl) { MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(5); writer.Write(AmongUsClient.Instance.GameId); writer.StartMessage(5); writer.WritePacked(playerControl.NetId); writer.EndMessage(); writer.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); AmongUsClient.Instance.RemoveNetObject(playerControl); Object.Destroy(playerControl.gameObject); } AllObjects.Remove(this); } catch (Exception e) { Utils.ThrowException(e); } } protected void Hide(PlayerControl player) { if (!AmongUsClient.Instance.AmHost) return; Logger.Info($" Hide Custom Net Object {GetType().Name} (ID {Id}) from {player.GetNameWithRole()}", "CNO.Hide"); if (player.AmOwner) { LateTask.New(() => playerControl.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(false), 0.1f); playerControl.Visible = false; return; } if (this is not ShapeshiftMenuElement) { LateTask.New(() => { var sender = CustomRpcSender.Create("FixModdedClientCNOText", SendOption.Reliable); sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.FixModdedClientCNO, player.OwnerId) .WriteNetObject(playerControl) .Write(false) .EndRpc(); sender.SendMessage(); }, 0.4f); } MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(6); writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(player.OwnerId); writer.StartMessage(5); writer.WritePacked(playerControl.NetId); writer.EndMessage(); writer.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); } protected virtual void OnFixedUpdate() { try { if (!AmongUsClient.Instance.AmHost) return; if (AmongUsClient.Instance.AmClient) { try { playerControl.NetTransform.SnapTo(Position, (ushort)(playerControl.NetTransform.lastSequenceId + 1U)); } catch { } } ushort num = (ushort)(playerControl.NetTransform.lastSequenceId + 2U); MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(playerControl.NetTransform.NetId, 21, SendOption.None); NetHelpers.WriteVector2(Position, messageWriter); messageWriter.Write(num); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } catch { } } protected void CreateNetObject(string sprite, Vector2 position) { if (GameStates.IsEnded || !AmongUsClient.Instance.AmHost) return; Logger.Info($" Create Custom Net Object {GetType().Name} (ID {MaxId + 1}) at {position} - Time since game start: {Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS}s", "CNO.CreateNetObject"); if (Options.CurrentGameMode == CustomGameMode.Standard && (!GameStates.InGame || !Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) { if (GameStates.InGame && (!Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) { Main.Instance.StartCoroutine(CoRoutine()); IEnumerator CoRoutine() { Logger.Info("Delaying CNO Spawn", "CustomNetObject.CreateNetObject"); while (GameStates.InGame && !GameStates.IsEnded && (!Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10)) yield return null; yield return new WaitForSecondsRealtime(3f); if (!GameStates.InGame || GameStates.IsEnded || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield break; CreateNetObject(sprite, position); } } return; } playerControl = Object.Instantiate(AmongUsClient.Instance.PlayerPrefab, Vector2.zero, Quaternion.identity); playerControl.PlayerId = 254; playerControl.isNew = false; playerControl.notRealPlayer = true; try { playerControl.NetTransform.SnapTo(new Vector2(50f, 50f)); } catch (Exception e) { Utils.ThrowException(e); } AmongUsClient.Instance.NetIdCnt += 1U; MessageWriter msg = MessageWriter.Get(SendOption.Reliable); msg.StartMessage(5); msg.Write(AmongUsClient.Instance.GameId); msg.StartMessage(4); SpawnGameDataMessage item = AmongUsClient.Instance.CreateSpawnMessage(playerControl, -2, SpawnFlags.None); item.SerializeValues(msg); msg.EndMessage(); if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) { for (uint i = 1; i <= 3; ++i) { msg.StartMessage(4); msg.WritePacked(2U); msg.WritePacked(-2); msg.Write((byte)SpawnFlags.None); msg.WritePacked(1); msg.WritePacked(AmongUsClient.Instance.NetIdCnt - i); msg.StartMessage(1); msg.EndMessage(); msg.EndMessage(); } } msg.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(msg); msg.Recycle(); if (PlayerControl.AllPlayerControls.Contains(playerControl)) PlayerControl.AllPlayerControls.Remove(playerControl); if (this is not ShapeshiftMenuElement) { LateTask.New(() => { string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; string skinId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId; string petId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId; string visorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId; var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject", SendOption.Reliable, log: false); MessageWriter writer = sender.stream; sender.StartMessage(); PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "
" + sprite; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = 0; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = ""; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = ""; writer.StartMessage(1); { writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); PlayerControl.LocalPlayer.Data.Serialize(writer, false); } writer.EndMessage(); try { playerControl.Shapeshift(PlayerControl.LocalPlayer, false); } catch (Exception e) { Utils.ThrowException(e); } sender.StartRpc(playerControl.NetId, (byte)RpcCalls.Shapeshift) .WriteNetObject(PlayerControl.LocalPlayer) .Write(false) .EndRpc(); PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = name; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId = colorId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId = hatId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].SkinId = skinId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PetId = petId; PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].VisorId = visorId; writer.StartMessage(1); { writer.WritePacked(PlayerControl.LocalPlayer.Data.NetId); PlayerControl.LocalPlayer.Data.Serialize(writer, false); } writer.EndMessage(); try { playerControl.NetTransform.SnapTo(Position); } catch (Exception e) { Utils.ThrowException(e); } sender.StartRpc(playerControl.NetTransform.NetId, (byte)RpcCalls.SnapTo) .WriteVector2(Position) .Write(playerControl.NetTransform.lastSequenceId) .EndRpc(); sender.EndMessage(); sender.SendMessage(); }, 0.2f); } playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; playerControl.cosmetics.colorBlindText.color = Color.clear; Position = position; Sprite = sprite; ++MaxId; Id = MaxId; if (MaxId == int.MaxValue) MaxId = int.MinValue; AllObjects.Add(this); LateTask.New(() => { foreach (PlayerControl pc in Main.EnumeratePlayerControls()) { if (pc.AmOwner) continue; var sender = CustomRpcSender.Create("CustomNetObject.CreateNetObject (2)", SendOption.Reliable, log: false); MessageWriter writer = sender.stream; sender.StartMessage(pc.OwnerId); writer.StartMessage(1); { writer.WritePacked(playerControl.NetId); writer.Write(pc.PlayerId); } writer.EndMessage(); sender.StartRpc(playerControl.NetId, (byte)RpcCalls.MurderPlayer) .WriteNetObject(playerControl) .Write((int)MurderResultFlags.FailedError) .EndRpc(); writer.StartMessage(1); { writer.WritePacked(playerControl.NetId); writer.Write((byte)254); } writer.EndMessage(); sender.EndMessage(); sender.SendMessage(); } playerControl.CachedPlayerData = PlayerControl.LocalPlayer.Data; }, 0.1f); LateTask.New(() => playerControl.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(true), 0.3f); // Fix for Host LateTask.New(() => Utils.SendRPC(CustomRPC.FixModdedClientCNO, playerControl, true), 0.6f); // Fix for Non-Host Modded } public virtual void OnMeeting() { if (!AmongUsClient.Instance.AmHost) return; Despawn(); Main.Instance.StartCoroutine(WaitForMeetingEnd()); return; IEnumerator WaitForMeetingEnd() { yield return new WaitForSecondsRealtime(10f); while (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield return null; yield return new WaitForSecondsRealtime(3f); while (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield return null; if (GameStates.IsEnded || !GameStates.InGame || GameStates.IsLobby) yield break; CreateNetObject(Sprite, Position); } } public static void FixedUpdate() { foreach (CustomNetObject cno in AllObjects.ToArray()) cno?.OnFixedUpdate(); } public static CustomNetObject Get(int id) { return AllObjects.FirstOrDefault(x => x.Id == id); } public static void Reset() { try { AllObjects.ToArray().Do(x => x.Despawn()); AllObjects.Clear(); } catch (Exception e) { Utils.ThrowException(e); } } public static void AfterMeeting() { AllObjects.OfType().ToArray().Do(x => x.Despawn()); } } internal sealed class TornadoObject : CustomNetObject { private readonly long SpawnTimeStamp; private bool Gone; public TornadoObject(Vector2 position, IEnumerable visibleList) { SpawnTimeStamp = Utils.TimeStamp; CreateNetObject("\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#636363>\u2588<#636363>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588
<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588
\u2588<#bababa>\u2588<#bababa>\u2588<#8c8c8c>\u2588<#8c8c8c>\u2588<#bababa>\u2588<#bababa>\u2588\u2588
\u2588\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588<#bababa>\u2588\u2588\u2588
", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.7f); } protected override void OnFixedUpdate() { base.OnFixedUpdate(); try { if (Gone) return; if (SpawnTimeStamp + Tornado.TornadoDuration.GetInt() < Utils.TimeStamp) { Gone = true; Despawn(); } } catch (NullReferenceException) { try { Despawn(); } finally { Gone = true; } } } public override void OnMeeting() { Despawn(); } } internal sealed class PlayerDetector : CustomNetObject { public PlayerDetector(Vector2 position, List visibleList, out int id) { CreateNetObject("\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588<#33e6b0>\u2588<#000000>\u2588<#000000>\u2588<#33e6b0>\u2588\u2588<#33e6b0>\u2588
<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588
\u2588<#33e6b0>\u2588\u2588\u2588\u2588\u2588<#33e6b0>\u2588\u2588
\u2588\u2588<#33e6b0>\u2588\u2588\u2588<#33e6b0>\u2588\u2588\u2588
", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.7f); id = Id; } } internal sealed class AdventurerItem : CustomNetObject { public readonly Adventurer.Resource Resource; internal AdventurerItem(Vector2 position, Adventurer.Resource resource, IEnumerable visibleList) { Resource = resource; (char Icon, Color Color) data = Adventurer.ResourceDisplayData[resource]; CreateNetObject($"{Utils.ColorString(data.Color, data.Icon.ToString())}", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.7f); } } internal sealed class Toilet : CustomNetObject { internal Toilet(Vector2 position, IEnumerable hideList) { CreateNetObject("\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588
\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588\u2588
\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588<#d3d4ce>\u2588<#dedede>\u2588<#dedede>\u2588<#d3d4ce>\u2588<#e6e6e6>\u2588<#e6e6e6>\u2588\u2588
\u2588<#bfbfbf>\u2588<#454545>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#333333>\u2588<#bfbfbf>\u2588
\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#454545>\u2588<#bfbfbf>\u2588
\u2588\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588<#bfbfbf>\u2588
\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588<#dedede>\u2588<#dedede>\u2588\u2588\u2588\u2588\u2588
", position); LateTask.New(() => hideList.Do(Hide), 0.7f); } } internal sealed class BlackHole : CustomNetObject { internal BlackHole(Vector2 position) { CreateNetObject("██<#000000>█<#19131c>█<#000000>█<#000000>███
█<#412847>█<#000000>█<#19131c>█<#000000>█<#412847>█<#260f26>██
<#000000>█<#412847>█<#412847>█<#000000>█<#260f26>█<#1c0d1c>█<#19131c>█<#000000>█
<#19131c>█<#000000>█<#412847>█<#1c0d1c>█<#1c0d1c>█<#000000>█<#19131c>█<#000000>█
<#000000>█<#000000>█<#260f26>█<#1c0d1c>█<#1c0d1c>█<#000000>█<#000000>█<#260f26>█
<#000000>█<#260f26>█<#1c0d1c>█<#1c0d1c>█<#19131c>█<#412847>█<#412847>█<#19131c>█
█<#260f26>█<#412847>█<#412847>█<#19131c>█<#260f26>█<#19131c>██
██<#412847>█<#260f26>█<#260f26>█<#000000>███
", position); } public override void OnMeeting() { if (Abyssbringer.ShouldDespawnCNOOnMeeting) Despawn(); else base.OnMeeting(); } } internal sealed class SprayedArea : CustomNetObject { public SprayedArea(Vector2 position, IEnumerable visibleList) { CreateNetObject("\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588
\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588
\u2588\u2588<#ffd000>\u2588<#ffd000>\u2588\u2588\u2588
", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().ExceptBy(visibleList, x => x.PlayerId).Do(Hide), 0.7f); } } internal sealed class CatcherTrap : CustomNetObject { public CatcherTrap(Vector2 position, PlayerControl catcher) { CreateNetObject("\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588
\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588
\u2588\u2588<#ccffda>\u2588<#ccffda>\u2588\u2588\u2588
", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().Without(catcher).Do(Hide), 0.7f); } } internal sealed class YellowFlag : CustomNetObject { public YellowFlag(Vector2 position) { CreateNetObject("<#000000>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588\u2588\u2588
<#000000>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588
<#000000>\u2588\u2588\u2588<#ffff00>\u2588<#ffff00>\u2588<#ffff00>\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
", position); } } internal sealed class BlueFlag : CustomNetObject { public BlueFlag(Vector2 position) { CreateNetObject("<#000000>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588\u2588\u2588
<#000000>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588
<#000000>\u2588\u2588\u2588<#0000ff>\u2588<#0000ff>\u2588<#0000ff>\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
<#000000>\u2588\u2588\u2588\u2588\u2588\u2588
", position); } } internal sealed class SoulObject : CustomNetObject { public SoulObject(Vector2 position, PlayerControl whisperer) { CreateNetObject("\u2588\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#cfcfcf>\u2588<#cfcfcf>\u2588
<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588
\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588<#fcfcfc>\u2588\u2588<#fcfcfc>\u2588
", position); LateTask.New(() => Main.EnumerateAlivePlayerControls().Without(whisperer).Do(Hide), 0.7f); } } internal sealed class DisasterWarningTimer : CustomNetObject { public DisasterWarningTimer(Vector2 position, float time, string disaster) { CreateNetObject($"{Math.Ceiling(time):N0}\n{disaster}", position); Disaster = disaster; Timer = time; } private string Disaster { get; } private float Timer { get; set; } private int Time => (int)Math.Ceiling(Timer); protected override void OnFixedUpdate() { base.OnFixedUpdate(); int oldTime = Time; if (oldTime <= 0) { Despawn(); return; } Timer -= UnityEngine.Time.fixedDeltaTime; if (Time != oldTime) RpcChangeSprite($"{Time:N0}\n{Disaster}"); } } public sealed class NaturalDisaster : CustomNetObject { public NaturalDisaster(Vector2 position, float time, string sprite, string disasterName, SystemTypes? room) { string name = Translator.GetString($"ND_{disasterName}"); if (room.HasValue) { name = $"<#ff0000>{name}"; Main.EnumerateAlivePlayerControls().DoIf(x => x.IsInRoom(room.Value), x => x.ReactorFlash()); } WarningTimer = new(position, time, name); SpawnTimer = time; Sprite = sprite; DisasterName = disasterName; Room = room; } public SystemTypes? Room { get; } public string DisasterName { get; } public float SpawnTimer { get; private set; } private DisasterWarningTimer WarningTimer { get; } public void Update() { if (float.IsNaN(SpawnTimer)) return; SpawnTimer -= Time.fixedDeltaTime; if (SpawnTimer <= 0f) { WarningTimer.Despawn(); CreateNetObject(Sprite, WarningTimer.Position); SpawnTimer = float.NaN; } } } internal sealed class Lightning : CustomNetObject { private float Timer = 5f; public Lightning(Vector2 position) { CreateNetObject("\u2588\u2588\u2588\u2588\u2588<#c6c7c3>\u2588
\u2588<#c6c7c3>\u2588\u2588\u2588<#c6c7c3>\u2588\u2588
<#c6c7c3>\u2588\u2588<#fffb00>\u2588<#fffb00>\u2588\u2588\u2588
\u2588\u2588<#fffb00>\u2588<#fffb00>\u2588\u2588<#c6c7c3>\u2588
<#c6c7c3>\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588<#c6c7c3>\u2588<#c6c7c3>\u2588\u2588\u2588
", position); } protected override void OnFixedUpdate() { base.OnFixedUpdate(); Timer -= Time.fixedDeltaTime; if (Timer <= 0) Despawn(); } } internal sealed class BlueBed : BedWars.Bed { public BlueBed(Vector2 position) { BaseSprite = "██████
<#c7deff>█<#c7deff>█<#00aeff>█<#00aeff>█<#00aeff>█<#00aeff>█
<#c7deff>█<#c7deff>█<#00aeff>█<#00aeff>█<#00aeff>█<#00aeff>█
<#c7deff>█<#c7deff>█<#00aeff>█<#00aeff>█<#00aeff>█<#00aeff>█
<#82531a>█████<#82531a>█
██████
"; UpdateStatus(false); CreateNetObject(GetSprite(), position); } } internal sealed class GreenBed : BedWars.Bed { public GreenBed(Vector2 position) { BaseSprite = "██████
<#baffd0>█<#baffd0>█<#00ff7b>█<#00ff7b>█<#00ff7b>█<#00ff7b>█
<#baffd0>█<#baffd0>█<#00ff7b>█<#00ff7b>█<#00ff7b>█<#00ff7b>█
<#baffd0>█<#baffd0>█<#00ff7b>█<#00ff7b>█<#00ff7b>█<#00ff7b>█
<#82531a>█████<#82531a>█
██████
"; UpdateStatus(false); CreateNetObject(GetSprite(), position); } } internal sealed class YellowBed : BedWars.Bed { public YellowBed(Vector2 position) { BaseSprite = "██████
<#fffebd>█<#fffebd>█<#ffff00>█<#ffff00>█<#ffff00>█<#ffff00>█
<#fffebd>█<#fffebd>█<#ffff00>█<#ffff00>█<#ffff00>█<#ffff00>█
<#fffebd>█<#fffebd>█<#ffff00>█<#ffff00>█<#ffff00>█<#ffff00>█
<#82531a>█████<#82531a>█
██████
"; UpdateStatus(false); CreateNetObject(GetSprite(), position); } } internal sealed class RedBed : BedWars.Bed { public RedBed(Vector2 position) { BaseSprite = "██████
<#ffbbb5>█<#ffbbb5>█<#ff0008>█<#ff0008>█<#ff0008>█<#ff0008>█
<#ffbbb5>█<#ffbbb5>█<#ff0008>█<#ff0008>█<#ff0008>█<#ff0008>█
<#ffbbb5>█<#ffbbb5>█<#ff0008>█<#ff0008>█<#ff0008>█<#ff0008>█
<#82531a>█████<#82531a>█
██████
"; UpdateStatus(false); CreateNetObject(GetSprite(), position); } } internal sealed class BedWarsItemGenerator : CustomNetObject { private readonly string ItemSprite; public BedWarsItemGenerator(Vector2 position, string itemSprite) { ItemSprite = itemSprite; CreateNetObject($"{itemSprite} 0", position); } public void SetCount(int count) { RpcChangeSprite($"{ItemSprite} {count}"); } } internal sealed class BedWarsShop : CustomNetObject { public BedWarsShop(Vector2 position, string sprite) { CreateNetObject(sprite, position); } } internal sealed class TNT : CustomNetObject { private readonly Vector2 Location; private float timer; public TNT(Vector2 location) { Location = location; timer = 4f; CreateNetObject("████<#000000>█<#ffea00>█
███<#000000>███
█<#ff0004>█<#ff0004>████
<#ff0004>█<#ff0004>█<#ff0004>█<#ff0004>███
<#ff0004>█<#ff0004>█<#ff0004>█<#ff0004>███
█<#ff0004>█<#ff0004>████
", location); } protected override void OnFixedUpdate() { base.OnFixedUpdate(); if (Options.CurrentGameMode != CustomGameMode.BedWars) return; timer -= Time.fixedDeltaTime; if (timer <= 0f) { BedWars.OnTNTExplode(Location); Despawn(); } } } internal sealed class Portal : CustomNetObject { public Portal(Vector2 position) { CreateNetObject("█<#2b006b>█<#2b006b>█<#2b006b>█<#2b006b>██
█<#2b006b>█<#fa69ff>█<#fa69ff>█<#2b006b>██
█<#2b006b>█<#fa69ff>█<#fa69ff>█<#2b006b>██
█<#2b006b>█<#fa69ff>█<#fa69ff>█<#2b006b>██
█<#2b006b>█<#fa69ff>█<#fa69ff>█<#2b006b>██
█<#2b006b>█<#2b006b>█<#2b006b>█<#2b006b>██
", position); } } internal sealed class Plant : CustomNetObject { public bool Spawned; public Plant(Vector2 position) { Position = position; Spawned = false; } public void SpawnIfNotSpawned() { if (Spawned) return; CreateNetObject("█<#00ff15>██<#00ff15>██<#00ff15>█
█<#00ff15>██<#00ff15>██<#00ff15>█
<#00ff15>█<#00ff15>██<#00ff15>██<#00ff15>█
<#00ff15>███<#00ff15>██<#00ff15>█
<#00ff15>██<#00ff15>█<#00ff15>██<#00ff15>█
<#00ff15>██<#00ff15>███<#00ff15>█
", Position); Spawned = true; } } internal sealed class Seed : CustomNetObject { public bool Spawned; private readonly string Color; public Seed(Vector2 position, string color) { Position = position; Spawned = false; Color = color; } public void SpawnIfNotSpawned() { if (Spawned) return; CreateNetObject($"█<#{Color}>██<#{Color}>██<#{Color}>█
█<#{Color}>██<#{Color}>██<#{Color}>█
<#{Color}>█<#{Color}>██<#{Color}>██<#{Color}>█
<#{Color}>███<#{Color}>██<#{Color}>█
<#{Color}>██<#{Color}>█<#{Color}>██<#{Color}>█
<#{Color}>██<#{Color}>███<#{Color}>█
", Position); Spawned = true; } } internal sealed class FallenTree : CustomNetObject { public FallenTree(Vector2 position) { CreateNetObject(Tree.FallenSprite, position); } } internal sealed class ShapeshiftMenuElement : CustomNetObject { public ShapeshiftMenuElement(byte visibleTo) { CreateNetObject(string.Empty, new Vector2(0f, 0f)); LateTask.New(() => Main.EnumeratePlayerControls().DoIf(x => x.PlayerId != visibleTo, Hide), 0.7f); } } public sealed class DeathracePowerUp : CustomNetObject { public readonly Deathrace.PowerUp PowerUp; public DeathracePowerUp(Vector2 position, Deathrace.PowerUp powerUp) { PowerUp = powerUp; char icon = powerUp switch { Deathrace.PowerUp.Smoke => '♨', Deathrace.PowerUp.Taser => '〄', Deathrace.PowerUp.EnergyDrink => '∂', Deathrace.PowerUp.Grenade => '♁', Deathrace.PowerUp.Ice => '☃', _ => throw new ArgumentOutOfRangeException(nameof(powerUp), powerUp, "Unhandled power-up type") }; Color color = powerUp switch { Deathrace.PowerUp.Smoke => new Color(0.5f, 0.5f, 0.5f), Deathrace.PowerUp.Taser => new Color(1f, 1f, 0f), Deathrace.PowerUp.EnergyDrink => new Color(1f, 0.5f, 0f), Deathrace.PowerUp.Grenade => new Color(1f, 0f, 0f), Deathrace.PowerUp.Ice => new Color(0f, 1f, 1f), _ => throw new ArgumentOutOfRangeException(nameof(powerUp), powerUp, "Unhandled power-up type") }; CreateNetObject(Utils.ColorString(color, $"{icon}\n{Translator.GetString($"Deathrace.PowerUpDisplay.{powerUp}").ToUpper()}"), position); } } internal sealed class Snowball : CustomNetObject { public PlayerControl Thrower; private Vector2 Direction; public bool Active; public Snowball(Vector2 from, Vector2 direction, PlayerControl thrower) { Thrower = thrower; Direction = direction; CreateNetObject("<#0000>WWWWW<#0000>W\nWWWWWW\nWWWWWW\nWWWWWW\nWWWWWW\n<#0000>WWWWW<#0000>W", from); Active = true; } protected override void OnFixedUpdate() { base.OnFixedUpdate(); if (!Active) return; Vector2 newPos = Position + Direction * Time.fixedDeltaTime * Snowdown.SnowballThrowSpeed; if ((PhysicsHelpers.AnythingBetween(Position, newPos, Constants.ShipOnlyMask, false)) || newPos.x < Snowdown.MapBounds.X.Left || newPos.x > Snowdown.MapBounds.X.Right || newPos.y < Snowdown.MapBounds.Y.Bottom || newPos.y > Snowdown.MapBounds.Y.Top) { SetInactive(); return; } TP(newPos); } public void SetInactive() { TP(new(50f, 50f)); Active = false; } public void Reuse(Vector2 from, Vector2 direction, PlayerControl thrower) { TP(from); Thrower = thrower; Direction = direction; Active = true; } } } // This method sometimes throws an exception, preventing further code from running // Fixed by wrapping each line in try-catch [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RawSetName))] static class RawSetNameErrorFixPatch { public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] string name) { try { __instance.gameObject.name = name; } catch { } try { __instance.cosmetics.SetName(name); } catch { } try { __instance.cosmetics.SetNameMask(true); } catch { } return false; } }