mirror of
https://github.com/Gurge44/EndlessHostRoles.git
synced 2026-10-19 15:51:58 +00:00
988 lines
44 KiB
C#
988 lines
44 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using AmongUs.InnerNet.GameDataMessages;
|
|
using EHR.Gamemodes;
|
|
using EHR.Modules;
|
|
using EHR.Roles;
|
|
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<CustomNetObject> AllObjects = [];
|
|
private static int MaxId = -1;
|
|
|
|
protected virtual bool ConstantlyChangesPosition => false;
|
|
|
|
protected int Id;
|
|
public PlayerControl playerControl;
|
|
public Vector2 Position;
|
|
private string Sprite;
|
|
|
|
private int SnapToSendFrameCount;
|
|
|
|
protected bool IsPooled;
|
|
|
|
public void RpcChangeSprite(string sprite)
|
|
{
|
|
if (!AmongUsClient.Instance.AmHost) return;
|
|
if (this is not NaturalDisaster nd || !nd.SpawnTimer.IsRunning) Logger.Info($" Change Custom Net Object {GetType().Name} (ID {Id}) sprite", "CNO.RpcChangeSprite");
|
|
|
|
bool notImportant = this is BedWarsItemGenerator || this is NaturalDisaster n && n.SpawnTimer.Elapsed.TotalSeconds < n.TotalWarningTime - 1 && Options.CurrentGameMode != CustomGameMode.NaturalDisasters && GameStates.CurrentServerType == GameStates.ServerType.Vanilla;
|
|
SendOption channel = notImportant ? SendOption.None : SendOption.Reliable;
|
|
|
|
DataFlagRateLimiter.Enqueue(() =>
|
|
{
|
|
if (!playerControl) return;
|
|
|
|
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", channel, log: false);
|
|
MessageWriter writer = sender.stream;
|
|
sender.StartMessage();
|
|
PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "<size=14><br></size>" + 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();
|
|
}, channel);
|
|
}
|
|
|
|
public void TP(Vector2 position)
|
|
{
|
|
Position = position;
|
|
}
|
|
|
|
private bool TryReusePooledObject(string sprite, Vector2 position)
|
|
{
|
|
try
|
|
{
|
|
if (this is not NaturalDisaster) return false;
|
|
|
|
CustomNetObject pooled = AllObjects.Find(x => x.IsPooled && x.playerControl);
|
|
if (pooled == null) return false;
|
|
|
|
Logger.Info($" Reusing pooled Custom Net Object NaturalDisaster (ID {pooled.Id})", "CNO.CreateNetObject");
|
|
|
|
playerControl = pooled.playerControl;
|
|
Id = pooled.Id;
|
|
IsPooled = false;
|
|
|
|
AllObjects.Remove(pooled);
|
|
AllObjects.Add(this);
|
|
|
|
TP(position);
|
|
RpcChangeSprite(sprite);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Utils.ThrowException(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Despawn(bool canPool = true)
|
|
{
|
|
if (!AmongUsClient.Instance.AmHost) return;
|
|
|
|
try
|
|
{
|
|
if (canPool && this is NaturalDisaster && playerControl)
|
|
{
|
|
Logger.Info($" Pooled Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn");
|
|
IsPooled = true;
|
|
TP(new Vector2(50f, 50f));
|
|
return;
|
|
}
|
|
|
|
Logger.Info($" Despawn Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn");
|
|
|
|
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(IEnumerable<PlayerControl> players)
|
|
{
|
|
int messages = 0;
|
|
|
|
MessageWriter packedWriter = MessageWriter.Get(SendOption.Reliable);
|
|
packedWriter.StartMessage(26);
|
|
packedWriter.WritePacked(AmongUsClient.Instance.GameId);
|
|
|
|
foreach (PlayerControl player in players)
|
|
{
|
|
if (packedWriter.Length > 500 || messages >= AmongUsClient.Instance.GetMaxMessagePackingLimit())
|
|
{
|
|
messages = 0;
|
|
packedWriter.EndMessage();
|
|
AmongUsClient.Instance.SendOrDisconnect(packedWriter);
|
|
packedWriter.Clear(SendOption.Reliable);
|
|
packedWriter.StartMessage(26);
|
|
packedWriter.WritePacked(AmongUsClient.Instance.GameId);
|
|
}
|
|
|
|
if (Hide(player, packedWriter))
|
|
messages++;
|
|
}
|
|
|
|
if (messages > 0)
|
|
{
|
|
packedWriter.EndMessage();
|
|
AmongUsClient.Instance.SendOrDisconnect(packedWriter);
|
|
}
|
|
|
|
packedWriter.Recycle();
|
|
}
|
|
|
|
private bool Hide(PlayerControl player, MessageWriter packedWriter)
|
|
{
|
|
if (!AmongUsClient.Instance.AmHost) return false;
|
|
Logger.Info($" Hide Custom Net Object {GetType().Name} (ID {Id}) from {player.GetNameWithRole()}", "CNO.Hide");
|
|
|
|
if (player.AmOwner)
|
|
{
|
|
LateTask.New(() => playerControl.transform.Find("Names").Find("NameText_TMP").gameObject.SetActive(false), 0.1f);
|
|
playerControl.Visible = false;
|
|
return false;
|
|
}
|
|
|
|
if (this is not ShapeshiftMenuElement && player.IsNonHostModdedClient())
|
|
{
|
|
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);
|
|
}
|
|
|
|
packedWriter.StartMessage(6);
|
|
packedWriter.Write(AmongUsClient.Instance.GameId);
|
|
packedWriter.WritePacked(player.OwnerId);
|
|
packedWriter.StartMessage(5);
|
|
packedWriter.WritePacked(playerControl.NetId);
|
|
packedWriter.EndMessage();
|
|
packedWriter.EndMessage();
|
|
|
|
return true;
|
|
}
|
|
|
|
protected virtual void OnFixedUpdate()
|
|
{
|
|
try
|
|
{
|
|
if (!AmongUsClient.Instance.AmHost) return;
|
|
|
|
if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla)
|
|
{
|
|
// max ~60 calls per second in total
|
|
int updateFrequency;
|
|
|
|
if (!ConstantlyChangesPosition)
|
|
{
|
|
updateFrequency = 30;
|
|
}
|
|
else
|
|
{
|
|
(int trueCount, int falseCount) = AllObjects.SplitCount(x => x.ConstantlyChangesPosition);
|
|
updateFrequency = trueCount / 2 + falseCount;
|
|
}
|
|
|
|
if (++SnapToSendFrameCount < updateFrequency) return;
|
|
SnapToSendFrameCount = 0;
|
|
}
|
|
|
|
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, IEnumerable<PlayerControl> hideFrom = null, PlayerControl onlyVisibleTo = null)
|
|
{
|
|
if (GameStates.IsEnded || !AmongUsClient.Instance.AmHost || TryReusePooledObject(sprite, position)) 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");
|
|
Main.Instance.StartCoroutine(CoRoutine());
|
|
return;
|
|
|
|
IEnumerator CoRoutine()
|
|
{
|
|
bool tooEarly = !Main.IntroDestroyed || Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 10;
|
|
|
|
if (Options.CurrentGameMode == CustomGameMode.Standard && (!GameStates.InGame || tooEarly))
|
|
{
|
|
if (GameStates.InGame && tooEarly)
|
|
{
|
|
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;
|
|
}
|
|
else
|
|
yield break;
|
|
}
|
|
|
|
var qa = DataFlagRateLimiter.Enqueue(() =>
|
|
{
|
|
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();
|
|
});
|
|
|
|
yield return qa.Wait();
|
|
if (qa.Dropped) yield break;
|
|
|
|
if (PlayerControl.AllPlayerControls.Contains(playerControl))
|
|
PlayerControl.AllPlayerControls.Remove(playerControl);
|
|
|
|
try { playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; } catch { }
|
|
try { playerControl.cosmetics.colorBlindText.color = Color.clear; } catch { }
|
|
Position = position;
|
|
Sprite = sprite;
|
|
++MaxId;
|
|
Id = MaxId;
|
|
if (MaxId == int.MaxValue) MaxId = int.MinValue;
|
|
|
|
AllObjects.Add(this);
|
|
|
|
yield return new WaitForSecondsRealtime(0.1f);
|
|
|
|
if (PlayerControl.AllPlayerControls.Count > 1)
|
|
{
|
|
int messages = 0;
|
|
MessageWriter stream = MessageWriter.Get(SendOption.Reliable);
|
|
stream.StartMessage(26);
|
|
stream.WritePacked(AmongUsClient.Instance.GameId);
|
|
|
|
foreach (PlayerControl pc in Main.EnumeratePlayerControls())
|
|
{
|
|
if (pc.AmOwner) continue;
|
|
|
|
if (stream.Length > 500 || messages + 3 > AmongUsClient.Instance.GetMaxMessagePackingLimit())
|
|
{
|
|
stream.EndMessage();
|
|
qa = DataFlagRateLimiter.Enqueue(() => AmongUsClient.Instance.SendOrDisconnect(stream), cleanup: stream.Recycle);
|
|
yield return qa.Wait();
|
|
if (qa.Dropped) yield break;
|
|
messages = 0;
|
|
stream.Clear(SendOption.Reliable);
|
|
stream.StartMessage(26);
|
|
stream.WritePacked(AmongUsClient.Instance.GameId);
|
|
}
|
|
|
|
stream.StartMessage(6);
|
|
stream.Write(AmongUsClient.Instance.GameId);
|
|
stream.WritePacked(pc.OwnerId);
|
|
stream.StartMessage(1);
|
|
stream.WritePacked(playerControl.NetId);
|
|
stream.Write(pc.PlayerId);
|
|
stream.EndMessage();
|
|
stream.StartMessage(2);
|
|
stream.WritePacked(playerControl.NetId);
|
|
stream.Write((byte)RpcCalls.MurderPlayer);
|
|
stream.WriteNetObject(playerControl);
|
|
stream.Write((int)MurderResultFlags.FailedError);
|
|
stream.EndMessage();
|
|
stream.StartMessage(1);
|
|
stream.WritePacked(playerControl.NetId);
|
|
stream.Write((byte)254);
|
|
stream.EndMessage();
|
|
stream.EndMessage();
|
|
|
|
messages += 3;
|
|
}
|
|
|
|
stream.EndMessage();
|
|
qa = DataFlagRateLimiter.Enqueue(() => AmongUsClient.Instance.SendOrDisconnect(stream));
|
|
yield return qa.Wait();
|
|
stream.Recycle();
|
|
if (qa.Dropped) yield break;
|
|
}
|
|
|
|
playerControl.CachedPlayerData = PlayerControl.LocalPlayer.Data;
|
|
|
|
if (hideFrom != null || onlyVisibleTo)
|
|
{
|
|
yield return new WaitForSecondsRealtime(0.3f);
|
|
Hide(onlyVisibleTo ? Main.EnumerateAlivePlayerControls().Without(onlyVisibleTo) : hideFrom);
|
|
}
|
|
|
|
yield return new WaitForSecondsRealtime(0.15f);
|
|
|
|
if (this is not ShapeshiftMenuElement)
|
|
{
|
|
yield return DataFlagRateLimiter.Enqueue(() =>
|
|
{
|
|
if (!playerControl) return;
|
|
|
|
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(onlyVisibleTo ? onlyVisibleTo.OwnerId : -1);
|
|
PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName = "<size=14><br></size>" + 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();
|
|
}).Wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
for (int index = AllObjects.Count - 1; index >= 0; index--)
|
|
AllObjects[index]?.OnFixedUpdate();
|
|
}
|
|
|
|
public static CustomNetObject Get(int id)
|
|
{
|
|
return AllObjects.Find(x => x.Id == id);
|
|
}
|
|
|
|
public static void Reset()
|
|
{
|
|
try
|
|
{
|
|
AllObjects.ToArray().Do(x => x.Despawn(canPool: false));
|
|
AllObjects.Clear();
|
|
}
|
|
catch (Exception e) { Utils.ThrowException(e); }
|
|
}
|
|
|
|
public static void AfterMeeting()
|
|
{
|
|
AllObjects.OfType<ShapeshiftMenuElement>().ToArray().Do(x => x.Despawn());
|
|
}
|
|
}
|
|
|
|
|
|
internal sealed class TornadoObject : CustomNetObject
|
|
{
|
|
private readonly long SpawnTimeStamp;
|
|
private bool Gone;
|
|
|
|
public TornadoObject(Vector2 position, PlayerControl tornado)
|
|
{
|
|
SpawnTimeStamp = Utils.TimeStamp;
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WW</color><mark=#bababa>WWWW</mark><#0000>WW\nW</color><mark=#bababa>WW</mark><mark=#8c8c8c>WW</mark><mark=#bababa>WW</mark><#0000>W</color>\n<mark=#bababa>WW</mark><mark=#8c8c8c>WWWW</mark><mark=#bababa>WW</mark>\n<mark=#bababa>W</mark><mark=#8c8c8c>WW</mark><mark=#636363>WW</mark><mark=#8c8c8c>WW</mark><mark=#bababa>W</mark>\n<mark=#bababa>W</mark><mark=#8c8c8c>WW</mark><mark=#636363>WW</mark><mark=#8c8c8c>WW</mark><mark=#bababa>W</mark>\n<mark=#bababa>WW</mark><mark=#8c8c8c>WWWW</mark><mark=#bababa>WW</mark>\n<#0000>W</color><mark=#bababa>WW</mark><mark=#8c8c8c>WW</mark><mark=#bababa>WW</mark><#0000>W\nWW</color><mark=#bababa>WWWW</mark><#0000>WW", position, onlyVisibleTo: tornado);
|
|
}
|
|
|
|
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, PlayerControl druid, out int id)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WW</color><mark=#33e6b0>W</mark><#0000>WW</color><mark=#33e6b0>W</mark><#0000>WW\nW</color><mark=#33e6b0>W</mark><#0000>WWWW</color><mark=#33e6b0>W</mark><#0000>W</color>\n<mark=#33e6b0>W</mark><#0000>WW</color><mark=#33e6b0>WW</mark><#0000>WW</color><mark=#33e6b0>W</mark>\n<mark=#33e6b0>W</mark><#0000>W</color><mark=#33e6b0>W</mark><mark=#000000>WW</mark><mark=#33e6b0>W</mark><#0000>W</color><mark=#33e6b0>W</mark>\n<mark=#33e6b0>W</mark><#0000>W</color><mark=#33e6b0>W</mark><mark=#000000>WW</mark><mark=#33e6b0>W</mark><#0000>W</color><mark=#33e6b0>W</mark>\n<mark=#33e6b0>W</mark><#0000>WW</color><mark=#33e6b0>WW</mark><#0000>WW</color><mark=#33e6b0>W</mark>\n<#0000>W</color><mark=#33e6b0>W</mark><#0000>WWWW</color><mark=#33e6b0>W</mark><#0000>W\nWW</color><mark=#33e6b0>W</mark><#0000>WW</color><mark=#33e6b0>W</mark><#0000>WW", position, onlyVisibleTo: druid);
|
|
id = Id;
|
|
}
|
|
}
|
|
|
|
internal sealed class AdventurerItem : CustomNetObject
|
|
{
|
|
public readonly Adventurer.Resource Resource;
|
|
|
|
internal AdventurerItem(Vector2 position, Adventurer.Resource resource, PlayerControl adventurer)
|
|
{
|
|
Resource = resource;
|
|
(char Icon, Color Color) data = Adventurer.ResourceDisplayData[resource];
|
|
CreateNetObject($"<size=300%><font=\"VCR SDF\"><line-height=67%>{Utils.ColorString(data.Color, data.Icon.ToString())}</line-height></font></size>", position, onlyVisibleTo: adventurer);
|
|
}
|
|
}
|
|
|
|
internal sealed class Toilet : CustomNetObject
|
|
{
|
|
internal Toilet(Vector2 position, IEnumerable<PlayerControl> hideList)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>W</color><mark=#e6e6e6>WWWWWWWW</mark><#0000>W</color>\n<mark=#e6e6e6>W</mark><mark=#d3d4ce>WWWWWWWW</mark><mark=#e6e6e6>W</mark>\n<mark=#e6e6e6>W</mark><mark=#d3d4ce>WWWWWWWW</mark><mark=#e6e6e6>W</mark>\n<#0000>W</color><mark=#e6e6e6>W</mark><mark=#d3d4ce>WWWWWW</mark><mark=#e6e6e6>W</mark><#0000>W\nW</color><mark=#e6e6e6>WW</mark><mark=#d3d4ce>W</mark><mark=#dedede>WW</mark><mark=#d3d4ce>W</mark><mark=#e6e6e6>WW</mark><#0000>W\nW</color><mark=#bfbfbf>W</mark><mark=#454545>W</mark><mark=#333333>WWWWWW</mark><mark=#bfbfbf>W</mark>\n<#0000>W</color><mark=#bfbfbf>WW</mark><mark=#454545>WWWWWW</mark><mark=#bfbfbf>W</mark>\n<#0000>WW</color><mark=#bfbfbf>WWWWWWWW</mark>\n<#0000>WWW</color><mark=#dedede>WWWW</mark><#0000>WWW\nWWWW</color><mark=#dedede>WW</mark><#0000>WWWW", position, hideList);
|
|
}
|
|
}
|
|
|
|
internal sealed class BlackHole : CustomNetObject
|
|
{
|
|
internal BlackHole(Vector2 position)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WW</color><mark=#000000>WWWW</mark><#0000>WW\nW</color><mark=#000000>WWWWWW</mark><#0000>W</color>\n<mark=#000000>WWWWWWWW\nWWWWWWWW\nWWWWWWWW\nWWWWWWWW</mark>\n<#0000>W</color><mark=#000000>WWWWWW</mark><#0000>W\nWW</color><mark=#000000>WWWW</mark><#0000>WW", position);
|
|
}
|
|
|
|
public override void OnMeeting()
|
|
{
|
|
if (Abyssbringer.ShouldDespawnCNOOnMeeting) Despawn();
|
|
else base.OnMeeting();
|
|
}
|
|
|
|
protected override bool ConstantlyChangesPosition => Abyssbringer.BlackHoleMovesTowardsNearestPlayer.GetBool();
|
|
}
|
|
|
|
internal sealed class SprayedArea : CustomNetObject
|
|
{
|
|
public SprayedArea(Vector2 position, PlayerControl sprayer)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WW</color><mark=#ffd000>WW</mark><#0000>WW\nW</color><mark=#ffd000>WWWW</mark><#0000>W</color>\n<mark=#ffd000>WWWWWW</mark>\n<mark=#ffd000>WWWWWW</mark>\n<#0000>W</color><mark=#ffd000>WWWW</mark><#0000>W\nWW</color><mark=#ffd000>WW</mark><#0000>WW", position, onlyVisibleTo: sprayer);
|
|
}
|
|
|
|
public override void OnMeeting()
|
|
{
|
|
Despawn();
|
|
}
|
|
}
|
|
|
|
internal sealed class CatcherTrap : CustomNetObject
|
|
{
|
|
public CatcherTrap(Vector2 position, PlayerControl catcher)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WW</color><mark=#ccffda>WW</mark><#0000>WW\nW</color><mark=#ccffda>WWWW</mark><#0000>W</color>\n<mark=#ccffda>WWWWWW</mark>\n<mark=#ccffda>WWWWWW</mark>\n<#0000>W</color><mark=#ccffda>WWWW</mark><#0000>W\nWW</color><mark=#ccffda>WW</mark><#0000>WW", position, onlyVisibleTo: catcher);
|
|
}
|
|
|
|
public override void OnMeeting()
|
|
{
|
|
Despawn();
|
|
}
|
|
}
|
|
|
|
internal sealed class YellowFlag : CustomNetObject
|
|
{
|
|
public YellowFlag(Vector2 position)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><mark=#000000>W</mark><mark=#ffff00>WWW</mark><#0000>WW</color>\n<mark=#000000>W</mark><mark=#ffff00>WWWWW</mark>\n<mark=#000000>W</mark><#0000>WW</color><mark=#ffff00>WWW</mark>\n<mark=#000000>W</mark><#0000>WWWWW</color>\n<mark=#000000>W</mark><#0000>WWWWW</color>\n<mark=#000000>W</mark><#0000>WWWWW", position);
|
|
}
|
|
}
|
|
|
|
internal sealed class BlueFlag : CustomNetObject
|
|
{
|
|
public BlueFlag(Vector2 position)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><mark=#000000>W</mark><mark=#0000ff>WWW</mark><#0000>WW</color>\n<mark=#000000>W</mark><mark=#0000ff>WWWWW</mark>\n<mark=#000000>W</mark><#0000>WW</color><mark=#0000ff>WWW</mark>\n<mark=#000000>W</mark><#0000>WWWWW</color>\n<mark=#000000>W</mark><#0000>WWWWW</color>\n<mark=#000000>W</mark><#0000>WWWWW", position);
|
|
}
|
|
}
|
|
|
|
internal sealed class SoulObject : CustomNetObject
|
|
{
|
|
public SoulObject(Vector2 position, PlayerControl whisperer)
|
|
{
|
|
CreateNetObject("<size=80%><line-height=97%><cspace=0.16em><#0000>WW</color><mark=#fcfcfc>WWW</mark><#0000>W\nW</color><mark=#fcfcfc>WWWWW</mark>\n<mark=#fcfcfc>WWWW</mark><mark=#cfcfcf>WW</mark>\n<mark=#fcfcfc>WWWWWW</mark>\n<#0000>W</color><mark=#fcfcfc>WWWWW</mark>\n<#0000>W</color><mark=#fcfcfc>WWW</mark><#0000>W</color><mark=#fcfcfc>W", position, onlyVisibleTo: whisperer);
|
|
}
|
|
}
|
|
|
|
public sealed class NaturalDisaster : CustomNetObject
|
|
{
|
|
public NaturalDisaster(Vector2 position, int time, string sprite, string disasterName, SystemTypes? room)
|
|
{
|
|
string name = Translator.GetString($"ND_{disasterName}");
|
|
string warning = $"<size=250%>{time}</size>\n{name}";
|
|
|
|
if (room.HasValue)
|
|
{
|
|
warning = $"<#ff4444>{warning}</color>";
|
|
|
|
try { Main.EnumerateAlivePlayerControls().DoIf(x => x.IsInRoom(room.Value), x => x.ReactorFlash()); }
|
|
catch (Exception e) { Utils.ThrowException(e); }
|
|
}
|
|
|
|
TotalWarningTime = time;
|
|
DisasterSprite = sprite;
|
|
DisasterName = disasterName;
|
|
DisasterNameTranslated = name;
|
|
Room = room;
|
|
|
|
SpawnTimer = Stopwatch.StartNew();
|
|
|
|
CreateNetObject(warning, position);
|
|
}
|
|
|
|
protected override bool ConstantlyChangesPosition => !SpawnTimer.IsRunning && DisasterName is "Tsunami" or "Tornado" && !IsPooled;
|
|
|
|
public SystemTypes? Room { get; }
|
|
public string DisasterName { get; }
|
|
public Stopwatch SpawnTimer { get; }
|
|
public int TotalWarningTime { get; }
|
|
private string DisasterSprite { get; }
|
|
|
|
private int TimeInt => (int)SpawnTimer.Elapsed.TotalSeconds;
|
|
private int PreviousTimeInt { get; set; }
|
|
private string DisasterNameTranslated { get; }
|
|
|
|
public void Update()
|
|
{
|
|
if (!SpawnTimer.IsRunning) return;
|
|
|
|
int newTime = TimeInt;
|
|
|
|
if (newTime >= TotalWarningTime)
|
|
{
|
|
SpawnTimer.Stop();
|
|
if (!Room.HasValue && !string.IsNullOrEmpty(DisasterSprite)) RpcChangeSprite(DisasterSprite);
|
|
}
|
|
else
|
|
{
|
|
if (PreviousTimeInt == newTime) return;
|
|
PreviousTimeInt = newTime;
|
|
string warning = $"<size=250%>{TotalWarningTime - newTime}</size>\n{DisasterNameTranslated}";
|
|
if (Room.HasValue) warning = $"<#ff4444>{warning}</color>";
|
|
RpcChangeSprite(warning);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class Lightning : CustomNetObject
|
|
{
|
|
private readonly Stopwatch Timer = Stopwatch.StartNew();
|
|
|
|
public Lightning(Vector2 position)
|
|
{
|
|
CreateNetObject("<line-height=97%><cspace=0.16em><#0000>WWWWW</color><mark=#c6c7c3>W</mark>\n<#0000>W</color><mark=#c6c7c3>W</mark><#0000>WW</color><mark=#c6c7c3>W</mark><#0000>W</color>\n<mark=#c6c7c3>W</mark><#0000>W</color><mark=#fffb00>WW</mark><#0000>WW\nWW</color><mark=#fffb00>WW</mark><#0000>W</color><mark=#c6c7c3>W</mark>\n<mark=#c6c7c3>W</mark><#0000>WWWWW\nWW</color><mark=#c6c7c3>WW</mark><#0000>WW", position);
|
|
}
|
|
|
|
protected override void OnFixedUpdate()
|
|
{
|
|
base.OnFixedUpdate();
|
|
if (Timer.Elapsed.TotalSeconds >= 3f) Despawn();
|
|
}
|
|
}
|
|
|
|
internal sealed class BlueBed : BedWars.Bed
|
|
{
|
|
public BlueBed(Vector2 position)
|
|
{
|
|
BaseSprite = "<size=70%><line-height=97%><cspace=0.16em><mark=#c7deff>WW</mark><mark=#00aeff>WWWW</mark>\n<mark=#c7deff>WW</mark><mark=#00aeff>WWWW</mark>\n<mark=#c7deff>WW</mark><mark=#00aeff>WWWW</mark>\n<mark=#82531a>W</mark><#0000>WWWW</color><mark=#82531a>W";
|
|
UpdateStatus(false);
|
|
CreateNetObject(GetSprite(), position);
|
|
}
|
|
}
|
|
|
|
internal sealed class GreenBed : BedWars.Bed
|
|
{
|
|
public GreenBed(Vector2 position)
|
|
{
|
|
BaseSprite = "<size=70%><line-height=97%><cspace=0.16em><mark=#baffd0>WW</mark><mark=#00ff7b>WWWW</mark>\n<mark=#baffd0>WW</mark><mark=#00ff7b>WWWW</mark>\n<mark=#baffd0>WW</mark><mark=#00ff7b>WWWW</mark>\n<mark=#82531a>W</mark><#0000>WWWW</color><mark=#82531a>W";
|
|
UpdateStatus(false);
|
|
CreateNetObject(GetSprite(), position);
|
|
}
|
|
}
|
|
|
|
internal sealed class YellowBed : BedWars.Bed
|
|
{
|
|
public YellowBed(Vector2 position)
|
|
{
|
|
BaseSprite = "<size=70%><line-height=97%><cspace=0.16em><mark=#fffebd>WW</mark><mark=#ffff00>WWWW</mark>\n<mark=#fffebd>WW</mark><mark=#ffff00>WWWW</mark>\n<mark=#fffebd>WW</mark><mark=#ffff00>WWWW</mark>\n<mark=#82531a>W</mark><#0000>WWWW</color><mark=#82531a>W";
|
|
UpdateStatus(false);
|
|
CreateNetObject(GetSprite(), position);
|
|
}
|
|
}
|
|
|
|
internal sealed class RedBed : BedWars.Bed
|
|
{
|
|
public RedBed(Vector2 position)
|
|
{
|
|
BaseSprite = "<size=70%><line-height=97%><cspace=0.16em><mark=#ffbbb5>WW</mark><mark=#ff0008>WWWW</mark>\n<mark=#ffbbb5>WW</mark><mark=#ff0008>WWWW</mark>\n<mark=#ffbbb5>WW</mark><mark=#ff0008>WWWW</mark>\n<mark=#82531a>W</mark><#0000>WWWW</color><mark=#82531a>W";
|
|
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("<line-height=97%><cspace=0.16em><#0000>WWWW</color><mark=#000000>W</mark><mark=#ffea00>W</mark>\n<#0000>WWW</color><mark=#000000>W</mark><#0000>WW\nW</color><mark=#ff0004>WW</mark><#0000>WWW</color>\n<mark=#ff0004>WWWW</mark><#0000>WW</color>\n<mark=#ff0004>WWWW</mark><#0000>WW\nW</color><mark=#ff0004>WW</mark><#0000>WWW", location);
|
|
}
|
|
|
|
protected override void OnFixedUpdate()
|
|
{
|
|
base.OnFixedUpdate();
|
|
if (Options.CurrentGameMode != CustomGameMode.BedWars) return;
|
|
timer -= Time.fixedDeltaTime;
|
|
|
|
if (timer <= 0f)
|
|
{
|
|
BedWars.OnTNTExplode(Location);
|
|
Despawn();
|
|
}
|
|
}
|
|
|
|
public override void OnMeeting()
|
|
{
|
|
if (Options.CurrentGameMode == CustomGameMode.BedWars)
|
|
base.OnMeeting();
|
|
else
|
|
Despawn();
|
|
}
|
|
}
|
|
|
|
internal sealed class Portal : CustomNetObject
|
|
{
|
|
public Portal(Vector2 position)
|
|
{
|
|
CreateNetObject("<size=70%><line-height=97%><cspace=0.16em><mark=#2b006b>WWWW</mark>\n<mark=#2b006b>W</mark><mark=#fa69ff>WW</mark><mark=#2b006b>W</mark>\n<mark=#2b006b>W</mark><mark=#fa69ff>WW</mark><mark=#2b006b>W</mark>\n<mark=#2b006b>W</mark><mark=#fa69ff>WW</mark><mark=#2b006b>W</mark>\n<mark=#2b006b>W</mark><mark=#fa69ff>WW</mark><mark=#2b006b>W</mark>\n<mark=#2b006b>WWWW", position);
|
|
}
|
|
}
|
|
|
|
internal sealed class Plant : CustomNetObject
|
|
{
|
|
public bool Spawned;
|
|
|
|
public Plant(Vector2 position)
|
|
{
|
|
Position = position;
|
|
Spawned = false;
|
|
}
|
|
|
|
public void SpawnIfNotSpawned()
|
|
{
|
|
if (Spawned) return;
|
|
CreateNetObject("<line-height=67%><alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<br><alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<br><#00ff15>█<#00ff15>█<alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<br><#00ff15>█<alpha=#00>█<alpha=#00>█<#00ff15>█<alpha=#00>█<#00ff15>█<br><#00ff15>█<alpha=#00>█<#00ff15>█<#00ff15>█<alpha=#00>█<#00ff15>█<br><#00ff15>█<alpha=#00>█<#00ff15>█<alpha=#00>█<alpha=#00>█<#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($"<size=100%><line-height=67%><alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<br><alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<br><#{Color}>█<#{Color}>█<alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<br><#{Color}>█<alpha=#00>█<alpha=#00>█<#{Color}>█<alpha=#00>█<#{Color}>█<br><#{Color}>█<alpha=#00>█<#{Color}>█<#{Color}>█<alpha=#00>█<#{Color}>█<br><#{Color}>█<alpha=#00>█<#{Color}>█<alpha=#00>█<alpha=#00>█<#{Color}>█<br></line-height></size>", Position);
|
|
Spawned = true;
|
|
}
|
|
}
|
|
|
|
internal sealed class FallenTree : CustomNetObject
|
|
{
|
|
public FallenTree(Vector2 position)
|
|
{
|
|
CreateNetObject(Tree.FallenSprite, position);
|
|
}
|
|
}
|
|
|
|
internal sealed class ShapeshiftMenuElement : CustomNetObject
|
|
{
|
|
public ShapeshiftMenuElement(PlayerControl guesser)
|
|
{
|
|
CreateNetObject(string.Empty, new Vector2(0f, 0f), onlyVisibleTo: guesser);
|
|
}
|
|
}
|
|
|
|
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<size=80%>{Translator.GetString($"Deathrace.PowerUpDisplay.{powerUp}").ToUpper()}</size>"), position);
|
|
}
|
|
}
|
|
|
|
internal sealed class Snowball : CustomNetObject
|
|
{
|
|
public PlayerControl Thrower;
|
|
private Vector2 Direction;
|
|
public bool Active;
|
|
|
|
protected override bool ConstantlyChangesPosition => Active;
|
|
|
|
public Snowball(Vector2 from, Vector2 direction, PlayerControl thrower)
|
|
{
|
|
Thrower = thrower;
|
|
Direction = direction;
|
|
CreateNetObject($"<size={Snowdown.SnowballSize * 100}%><line-height=97%><cspace=0.16em><#0000>W</color><mark=#e4fdff>WWWW</mark><#0000>W</color>\n<mark=#e4fdff>WWWWWW</mark>\n<mark=#e4fdff>WWWWWW</mark>\n<mark=#e4fdff>WWWWWW</mark>\n<mark=#e4fdff>WWWWWW</mark>\n<#0000>W</color><mark=#e4fdff>WWWW</mark><#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;
|
|
}
|
|
}
|
|
} |