mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
Rework debugger (#79)
This commit is contained in:
1 parent
d82268b88a
commit
5db20ca9de
27 files changed
+1068
-180
No files matched your search
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using InnerNet;
|
||||
using Reactor.Debugger.AutoJoin.Messages;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin;
|
||||
|
||||
internal sealed class AutoJoinClientConnection : AutoJoinConnection
|
||||
{
|
||||
public const string PipeName = "Reactor.Debugger.AutoJoin";
|
||||
|
||||
private AutoJoinClientConnection(NamedPipeClientStream pipe) : base(pipe)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Handle(BinaryReader reader, MessageType messageType)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case MessageType.JoinGame:
|
||||
{
|
||||
Handle(JoinGameMessage.Deserialize(reader));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Handle(in JoinGameMessage message)
|
||||
{
|
||||
var (address, port, gameCode) = message;
|
||||
|
||||
Dispatcher.Instance.Enqueue(() =>
|
||||
{
|
||||
var gameCodeText = gameCode == InnerNetServer.LocalGameId ? "<local>" : GameCode.IntToGameName(gameCode);
|
||||
Info($"Joining {gameCodeText} on {address}:{port}");
|
||||
|
||||
if (gameCode == InnerNetServer.LocalGameId)
|
||||
{
|
||||
AmongUsClient.Instance.NetworkMode = NetworkModes.LocalGame;
|
||||
AmongUsClient.Instance.GameId = gameCode;
|
||||
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoConnectToGameServer(MatchMakerModes.Client, address, port, null));
|
||||
}
|
||||
else
|
||||
{
|
||||
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameDirect(gameCode, address, port, null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SendRequestJoin()
|
||||
{
|
||||
Write(default(RequestJoinGameMessage));
|
||||
}
|
||||
|
||||
public static bool TryConnect([NotNullWhen(true)] out AutoJoinClientConnection? client)
|
||||
{
|
||||
var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
|
||||
|
||||
try
|
||||
{
|
||||
pipeClient.Connect(100);
|
||||
|
||||
client = new AutoJoinClientConnection(pipeClient);
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
pipeClient.Dispose();
|
||||
|
||||
client = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading.Tasks;
|
||||
using Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin;
|
||||
|
||||
internal abstract class AutoJoinConnection : IDisposable
|
||||
{
|
||||
private readonly BinaryWriter _writer;
|
||||
|
||||
protected AutoJoinConnection(PipeStream pipe)
|
||||
{
|
||||
_writer = new BinaryWriter(pipe);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new BinaryReader(pipe);
|
||||
|
||||
while (pipe.IsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageType = (MessageType) reader.ReadByte();
|
||||
Debug($"Received {messageType}");
|
||||
Handle(reader, messageType);
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pipe.Dispose();
|
||||
Disconnected?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public event Action? Disconnected;
|
||||
|
||||
protected abstract void Handle(BinaryReader reader, MessageType messageType);
|
||||
|
||||
public void Write<T>(in T message) where T : IMessage<T>
|
||||
{
|
||||
Debug($"Writing {message}");
|
||||
_writer.Write(message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writer.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using InnerNet;
|
||||
using Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin;
|
||||
|
||||
internal sealed class AutoJoinConnectionListener : IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly Mutex _mutex;
|
||||
|
||||
private AutoJoinConnectionListener(Mutex mutex)
|
||||
{
|
||||
_mutex = mutex;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
Info("Listening for connections");
|
||||
|
||||
var pipeServer = new NamedPipeServerStream(
|
||||
AutoJoinClientConnection.PipeName,
|
||||
PipeDirection.InOut,
|
||||
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||
PipeTransmissionMode.Byte,
|
||||
PipeOptions.Asynchronous
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
await pipeServer.WaitForConnectionAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
pipeServer.Dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
Info("Client connected");
|
||||
|
||||
var client = new AutoJoinServerConnection(pipeServer);
|
||||
Clients.Add(client);
|
||||
client.Disconnected += () =>
|
||||
{
|
||||
Clients.Remove(client);
|
||||
client.Dispose();
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Info("Stopped");
|
||||
Stopped?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public event Action? Stopped;
|
||||
|
||||
public List<AutoJoinServerConnection> Clients { get; } = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
foreach (var client in Clients)
|
||||
{
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
Clients.Clear();
|
||||
|
||||
_mutex.Dispose();
|
||||
}
|
||||
|
||||
public void SendJoinMe(InnerNetClient innerNetClient)
|
||||
{
|
||||
var joinGameMessage = JoinGameMessage.From(innerNetClient);
|
||||
|
||||
foreach (var client in Clients)
|
||||
{
|
||||
client.Write(joinGameMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryStart([NotNullWhen(true)] out AutoJoinConnectionListener? connectionListener)
|
||||
{
|
||||
var mutex = new Mutex(false, $@"Global\{AutoJoinClientConnection.PipeName}");
|
||||
|
||||
if (mutex.WaitOne(100))
|
||||
{
|
||||
connectionListener = new AutoJoinConnectionListener(mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
mutex.Dispose();
|
||||
|
||||
connectionListener = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin;
|
||||
|
||||
internal static class AutoJoinConnectionManager
|
||||
{
|
||||
public static AutoJoinConnectionListener? Server { get; set; }
|
||||
public static AutoJoinClientConnection? Client { get; set; }
|
||||
|
||||
public static void StartOrConnect()
|
||||
{
|
||||
if (AutoJoinConnectionListener.TryStart(out var connectionListener))
|
||||
{
|
||||
Info("Started an AutoJoin session");
|
||||
Server = connectionListener;
|
||||
Server.Stopped += () =>
|
||||
{
|
||||
if (Server == connectionListener)
|
||||
{
|
||||
Server = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (AutoJoinClientConnection.TryConnect(out var client))
|
||||
{
|
||||
Info("Connected to an AutoJoin session");
|
||||
Client = client;
|
||||
client.Disconnected += () =>
|
||||
{
|
||||
if (Client == client)
|
||||
{
|
||||
Client = null;
|
||||
|
||||
Info("Disconnected from an AutoJoin session, trying to reconnect");
|
||||
StartOrConnect();
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Error("Failed to start or connect to an AutoJoin session");
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Start))]
|
||||
public static class ConnectPatch
|
||||
{
|
||||
public static void Postfix(InnerNetClient __instance)
|
||||
{
|
||||
if (!DebuggerConfig.JoinGameOnStart.Value) return;
|
||||
|
||||
if (__instance.TryCast<AmongUsClient>() is { } amongUsClient)
|
||||
{
|
||||
if (Server != null)
|
||||
{
|
||||
amongUsClient.StartCoroutine(amongUsClient.CoCreateOnlineGame());
|
||||
}
|
||||
else if (Client is { } client)
|
||||
{
|
||||
client.SendRequestJoin();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin;
|
||||
|
||||
internal sealed class AutoJoinServerConnection : AutoJoinConnection
|
||||
{
|
||||
public AutoJoinServerConnection(NamedPipeServerStream pipe) : base(pipe)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Handle(BinaryReader reader, MessageType messageType)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case MessageType.RequestJoinGame:
|
||||
{
|
||||
Handle(RequestJoinGameMessage.Deserialize(reader));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle(in RequestJoinGameMessage message)
|
||||
{
|
||||
if (AmongUsClient.Instance.AmConnected)
|
||||
{
|
||||
Write(JoinGameMessage.From(AmongUsClient.Instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
internal static class Extensions
|
||||
{
|
||||
public static void Write<T>(this BinaryWriter writer, in T message) where T : IMessage<T>
|
||||
{
|
||||
writer.Write((byte) T.Type);
|
||||
message.Serialize(writer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma warning disable CA2252 // TODO Remove after BepInEx updates the .NET version
|
||||
using System.IO;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
internal interface IMessage<TSelf> where TSelf : IMessage<TSelf>
|
||||
{
|
||||
static abstract MessageType Type { get; }
|
||||
|
||||
void Serialize(BinaryWriter writer);
|
||||
|
||||
static abstract TSelf Deserialize(BinaryReader reader);
|
||||
|
||||
string ToString();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.IO;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
internal readonly record struct JoinGameMessage(
|
||||
string Address,
|
||||
ushort Port,
|
||||
int GameCode
|
||||
) : IMessage<JoinGameMessage>
|
||||
{
|
||||
public static MessageType Type => MessageType.JoinGame;
|
||||
|
||||
public void Serialize(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Address);
|
||||
writer.Write(Port);
|
||||
writer.Write(GameCode);
|
||||
}
|
||||
|
||||
public static JoinGameMessage Deserialize(BinaryReader reader)
|
||||
{
|
||||
return new JoinGameMessage
|
||||
{
|
||||
Address = reader.ReadString(),
|
||||
Port = reader.ReadUInt16(),
|
||||
GameCode = reader.ReadInt32(),
|
||||
};
|
||||
}
|
||||
|
||||
public static JoinGameMessage From(InnerNetClient innerNetClient)
|
||||
{
|
||||
return new JoinGameMessage
|
||||
{
|
||||
Address = innerNetClient.networkAddress,
|
||||
Port = (ushort) innerNetClient.networkPort,
|
||||
GameCode = innerNetClient.GameId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
internal enum MessageType : byte
|
||||
{
|
||||
JoinGame,
|
||||
RequestJoinGame,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Reactor.Debugger.AutoJoin.Messages;
|
||||
|
||||
internal readonly record struct RequestJoinGameMessage : IMessage<RequestJoinGameMessage>
|
||||
{
|
||||
public static MessageType Type => MessageType.RequestJoinGame;
|
||||
|
||||
public void Serialize(BinaryWriter writer)
|
||||
{
|
||||
}
|
||||
|
||||
public static RequestJoinGameMessage Deserialize(BinaryReader reader)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using BepInEx.Configuration;
|
||||
|
||||
namespace Reactor.Debugger;
|
||||
|
||||
internal static class DebuggerConfig
|
||||
{
|
||||
public const string FeaturesSection = "Features";
|
||||
public const string AutoJoinSection = "AutoJoin";
|
||||
|
||||
public static ConfigEntry<bool> DisableGameEnd { get; private set; } = null!;
|
||||
public static ConfigEntry<bool> RedirectLogger { get; private set; } = null!;
|
||||
public static ConfigEntry<bool> AutoPlayAgain { get; private set; } = null!;
|
||||
public static ConfigEntry<bool> DisableTimeout { get; private set; } = null!;
|
||||
|
||||
public static ConfigEntry<bool> JoinGameOnStart { get; private set; } = null!;
|
||||
|
||||
public static void Bind(ConfigFile config)
|
||||
{
|
||||
DisableGameEnd = config.Bind(FeaturesSection, nameof(DisableGameEnd), false, "Stops the game from ending regardless of conditions");
|
||||
RedirectLogger = config.Bind(FeaturesSection, nameof(RedirectLogger), false, "Redirect base game Logger calls into BepInEx logging");
|
||||
AutoPlayAgain = config.Bind(FeaturesSection, nameof(AutoPlayAgain), true, "Automatically calls Play Again after game ends");
|
||||
DisableTimeout = config.Bind(FeaturesSection, nameof(DisableTimeout), false, "Disable the network disconnection timeout");
|
||||
|
||||
JoinGameOnStart = config.Bind(AutoJoinSection, nameof(JoinGameOnStart), false, "Automatically hosts/joins a game on start");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using AmongUs.GameOptions;
|
||||
global using static Reactor.Utilities.Logger<Reactor.Debugger.DebuggerPlugin>;
|
||||
using BepInEx;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using InnerNet;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.ImGui;
|
||||
using UnityEngine;
|
||||
using Reactor.Debugger.AutoJoin;
|
||||
using Reactor.Debugger.Patches;
|
||||
using Reactor.Debugger.Window;
|
||||
|
||||
namespace Reactor.Debugger;
|
||||
|
||||
@@ -20,107 +14,17 @@ namespace Reactor.Debugger;
|
||||
public partial class DebuggerPlugin : BasePlugin
|
||||
{
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
public DebuggerComponent Component { get; private set; } = null!;
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
Component = this.AddComponent<DebuggerComponent>();
|
||||
DebuggerConfig.Bind(Config);
|
||||
|
||||
var maxImpostors = (Il2CppStructArray<int>) Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = maxImpostors;
|
||||
NormalGameOptionsV07.MaxImpostors = NormalGameOptionsV07.RecommendedImpostors = maxImpostors;
|
||||
HideNSeekGameOptionsV07.MaxImpostors = maxImpostors;
|
||||
this.AddComponent<DebuggerWindow>();
|
||||
|
||||
var minPlayers = (Il2CppStructArray<int>) Enumerable.Repeat(1, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MinPlayers = minPlayers;
|
||||
NormalGameOptionsV07.MinPlayers = minPlayers;
|
||||
HideNSeekGameOptionsV07.MinPlayers = minPlayers;
|
||||
GameOptionsPatches.Initialize();
|
||||
|
||||
Harmony.PatchAll();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class DebuggerComponent : MonoBehaviour
|
||||
{
|
||||
[HideFromIl2Cpp]
|
||||
public bool DisableGameEnd { get; set; }
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public DragWindow TestWindow { get; }
|
||||
|
||||
public DebuggerComponent(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
TestWindow = new DragWindow(new Rect(20, 20, 0, 0), "Debugger", () =>
|
||||
{
|
||||
GUILayout.Label("Name: " + DataManager.Player.Customization.Name);
|
||||
DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end");
|
||||
|
||||
if (ShipStatus.Instance && AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
if (GUILayout.Button("Force game end"))
|
||||
{
|
||||
ShipStatus.Instance.enabled = false;
|
||||
GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Call a meeting"))
|
||||
{
|
||||
PlayerControl.LocalPlayer.CmdReportDeadBody(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (TutorialManager.InstanceExists && PlayerControl.LocalPlayer)
|
||||
{
|
||||
var data = PlayerControl.LocalPlayer.Data;
|
||||
|
||||
var newIsImpostor = GUILayout.Toggle(data.Role.IsImpostor, "Is Impostor");
|
||||
if (data.Role.IsImpostor != newIsImpostor)
|
||||
{
|
||||
PlayerControl.LocalPlayer.RpcSetRole(newIsImpostor ? RoleTypes.Impostor : RoleTypes.Crewmate);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Spawn a dummy"))
|
||||
{
|
||||
var playerControl = Instantiate(TutorialManager.Instance.PlayerPrefab);
|
||||
var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
|
||||
GameData.Instance.AddPlayer(playerControl);
|
||||
AmongUsClient.Instance.Spawn(playerControl, -2, SpawnFlags.None);
|
||||
playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
|
||||
playerControl.GetComponent<DummyBehaviour>().enabled = true;
|
||||
playerControl.NetTransform.enabled = false;
|
||||
playerControl.SetName($"{TranslationController.Instance.GetString(StringNames.Dummy, Array.Empty<Il2CppSystem.Object>())} {i}");
|
||||
var color = (byte) (i % Palette.PlayerColors.Length);
|
||||
playerControl.SetColor(color);
|
||||
playerControl.SetHat(HatManager.Instance.allHats[i % HatManager.Instance.allHats.Count].ProdId, playerControl.Data.DefaultOutfit.ColorId);
|
||||
playerControl.SetPet(HatManager.Instance.allPets[i % HatManager.Instance.allPets.Count].ProdId);
|
||||
playerControl.SetSkin(HatManager.Instance.allSkins[i % HatManager.Instance.allSkins.Count].ProdId, color);
|
||||
GameData.Instance.RpcSetTasks(playerControl.PlayerId, new Il2CppStructArray<byte>(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer)
|
||||
{
|
||||
var position = PlayerControl.LocalPlayer.transform.position;
|
||||
GUILayout.Label($"x: {position.x}");
|
||||
GUILayout.Label($"y: {position.y}");
|
||||
}
|
||||
})
|
||||
{
|
||||
Enabled = false,
|
||||
};
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F1))
|
||||
{
|
||||
TestWindow.Enabled = !TestWindow.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
TestWindow.OnGUI();
|
||||
}
|
||||
AutoJoinConnectionManager.StartOrConnect();
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger;
|
||||
|
||||
internal static class Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class UpdatePatch
|
||||
{
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
__instance.MinPlayers = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class CountdownPatch
|
||||
{
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
__instance.countDownTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
|
||||
public static class AmBannedPatch
|
||||
{
|
||||
public static void Postfix(out bool __result)
|
||||
{
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class CheckEndCriteriaPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(LogicGameFlow), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
[HarmonyPatch(typeof(LogicGameFlowHnS), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
public static bool Prefix()
|
||||
{
|
||||
return !PluginSingleton<DebuggerPlugin>.Instance.Component.DisableGameEnd;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
|
||||
public static class GameSettingMenuPatch
|
||||
{
|
||||
public static void Prefix(GameSettingMenu __instance)
|
||||
{
|
||||
// Unlocks map/impostor amount changing in online (for testing on your custom servers)
|
||||
__instance.HideForOnline = new Il2CppReferenceArray<Transform>(0);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class GameOptionsMenuPatch
|
||||
{
|
||||
public static void Postfix(GameOptionsMenu __instance)
|
||||
{
|
||||
if (GameOptionsManager.Instance.CurrentGameOptions.GameMode != GameModes.Normal) return;
|
||||
|
||||
__instance.Children
|
||||
.Single(o => o.Title == StringNames.GameNumImpostors)
|
||||
.Cast<NumberOption>()
|
||||
.ValidRange = new FloatRange(0, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(EndGameNavigation), nameof(EndGameNavigation.ShowDefaultNavigation))]
|
||||
internal static class AutoPlayAgainPatch
|
||||
{
|
||||
public static void Postfix(EndGameNavigation __instance)
|
||||
{
|
||||
if (!DebuggerConfig.AutoPlayAgain.Value) return;
|
||||
|
||||
__instance.NextGame();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
internal static class DisableGameEndPatch
|
||||
{
|
||||
private static bool Enabled => DebuggerConfig.DisableGameEnd.Value;
|
||||
|
||||
[HarmonyPatch(typeof(LogicGameFlow), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
[HarmonyPatch(typeof(LogicGameFlowHnS), nameof(LogicGameFlow.CheckEndCriteria))]
|
||||
public static bool Prefix()
|
||||
{
|
||||
return !Enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using HarmonyLib;
|
||||
using Hazel.Udp;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class DisableTimeoutPatch
|
||||
{
|
||||
private static bool Enabled => DebuggerConfig.DisableTimeout.Value;
|
||||
|
||||
[HarmonyPatch(typeof(UdpConnection), nameof(UdpConnection.HandleKeepAlive))]
|
||||
[HarmonyPrefix]
|
||||
public static bool DisableKeepAlive()
|
||||
{
|
||||
return !Enabled;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(UnityUdpClientConnection), nameof(UnityUdpClientConnection.ConnectAsync))]
|
||||
[HarmonyPostfix]
|
||||
public static void DisableTimeout(UnityUdpClientConnection __instance)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
__instance.DisconnectTimeoutMs = int.MaxValue;
|
||||
__instance.ResendLimit = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Il2CppSystem.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class GameOptionsPatches
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
var maxImpostors = (Il2CppStructArray<int>) Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MaxImpostors = maxImpostors;
|
||||
NormalGameOptionsV07.MaxImpostors = maxImpostors;
|
||||
HideNSeekGameOptionsV07.MaxImpostors = maxImpostors;
|
||||
|
||||
var minPlayers = (Il2CppStructArray<int>) Enumerable.Repeat(1, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MinPlayers = minPlayers;
|
||||
NormalGameOptionsV07.MinPlayers = minPlayers;
|
||||
HideNSeekGameOptionsV07.MinPlayers = minPlayers;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
|
||||
[HarmonyPrefix]
|
||||
public static void UnlockAllOptions(GameSettingMenu __instance)
|
||||
{
|
||||
__instance.HideForOnline = new Il2CppReferenceArray<Transform>(0);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.OnEnable))]
|
||||
[HarmonyPostfix]
|
||||
public static void UnlockOptionRange(NumberOption __instance)
|
||||
{
|
||||
__instance.ValidRange.min = float.MinValue;
|
||||
__instance.ValidRange.max = float.MaxValue;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.SetImpostorButtons))]
|
||||
public static class DisableImpostorCountReset
|
||||
{
|
||||
private static readonly MethodInfo _refreshMethod = Il2CppType.Of<CreateOptionsPicker>().GetMethod("Refresh", BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
foreach (var stackFrame in new Il2CppSystem.Diagnostics.StackTrace().GetFrames())
|
||||
{
|
||||
if (_refreshMethod.Equals(stackFrame.GetMethod()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(CrewVisualizer), nameof(CrewVisualizer.SetCrewSize))]
|
||||
[HarmonyPrefix]
|
||||
public static void SetCrewSizePatch(int numPlayers, ref int numImpostors)
|
||||
{
|
||||
if (numImpostors >= numPlayers)
|
||||
{
|
||||
numImpostors = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(GameStartManager))]
|
||||
internal static class GameStartManagerPatches
|
||||
{
|
||||
[HarmonyPatch(nameof(GameStartManager.Start))]
|
||||
[HarmonyPrefix]
|
||||
public static void StartPatch(GameStartManager __instance)
|
||||
{
|
||||
__instance.MinPlayers = 1;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(GameStartManager.Update))]
|
||||
[HarmonyPrefix]
|
||||
public static void UpdatePatch(GameStartManager __instance)
|
||||
{
|
||||
__instance.countDownTimer = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class MiscellaneousPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
|
||||
[HarmonyPrefix]
|
||||
public static void AmBannedPatch(out bool __result)
|
||||
{
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class RandomizeFreeplayServerPortPatch
|
||||
{
|
||||
private static ushort? _lastPort;
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.StartAsLocalServer))]
|
||||
public static class StartServerPatch
|
||||
{
|
||||
public static void Prefix(InnerNetServer __instance)
|
||||
{
|
||||
var port = (ushort) Random.Shared.Next(1024, IPEndPoint.MaxPort);
|
||||
_lastPort = port;
|
||||
__instance.Port = port;
|
||||
}
|
||||
|
||||
public static void Postfix(InnerNetServer __instance)
|
||||
{
|
||||
__instance.Port = Constants.GamePlayPort;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SetEndpoint))]
|
||||
[HarmonyPrefix]
|
||||
public static void SetEndpointPatch(InnerNetClient __instance, string addr, ref ushort port)
|
||||
{
|
||||
if (_lastPort != null && __instance.NetworkMode == NetworkModes.FreePlay)
|
||||
{
|
||||
port = _lastPort.Value;
|
||||
_lastPort = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Reactor.Debugger.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(Logger))]
|
||||
internal static class RedirectLoggerPatch
|
||||
{
|
||||
private static readonly ManualLogSource _log = BepInEx.Logging.Logger.CreateLogSource("Among Us");
|
||||
|
||||
private static bool Enabled => DebuggerConfig.RedirectLogger.Value;
|
||||
|
||||
private static void Log(Logger logger, Logger.Level level, Il2CppSystem.Object message, Object? context = null)
|
||||
{
|
||||
var finalMessage = new StringBuilder();
|
||||
|
||||
if (logger.category != Logger.Category.None) finalMessage.Append(CultureInfo.InvariantCulture, $"[{logger.category}] ");
|
||||
if (!string.IsNullOrEmpty(logger.tag)) finalMessage.Append(CultureInfo.InvariantCulture, $"[{logger.tag}] ");
|
||||
if (context != null) finalMessage.Append(CultureInfo.InvariantCulture, $"[{context.name} ({context.GetIl2CppType().FullName})]");
|
||||
finalMessage.Append(CultureInfo.InvariantCulture, $" {message.ToString()} ");
|
||||
|
||||
_log.Log(
|
||||
level switch
|
||||
{
|
||||
Logger.Level.Debug => LogLevel.Debug,
|
||||
Logger.Level.Error => LogLevel.Error,
|
||||
Logger.Level.Warning => LogLevel.Warning,
|
||||
Logger.Level.Info => LogLevel.Info,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
|
||||
},
|
||||
finalMessage
|
||||
);
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(Logger.Debug))]
|
||||
[HarmonyPrefix]
|
||||
public static bool DebugPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
Log(__instance, Logger.Level.Debug, message, context);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(Logger.Info))]
|
||||
[HarmonyPrefix]
|
||||
public static bool InfoPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
Log(__instance, Logger.Level.Info, message, context);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(Logger.Warning))]
|
||||
[HarmonyPrefix]
|
||||
public static bool WarningPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
Log(__instance, Logger.Level.Warning, message, context);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(Logger.Error))]
|
||||
[HarmonyPrefix]
|
||||
public static bool ErrorPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
Log(__instance, Logger.Level.Error, message, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Il2CppInterop.Runtime;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Debugger.Utilities;
|
||||
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IEnumerator CoCreateLocalGame(this AmongUsClient client)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.NetworkMode = NetworkModes.LocalGame;
|
||||
|
||||
InnerNetServer.Instance.StartAsServer();
|
||||
|
||||
client.SetEndpoint(Constants.LocalNetAddress, Constants.GamePlayPort, false);
|
||||
client.MainMenuScene = "MatchMaking";
|
||||
client.OnlineScene = "OnlineGame";
|
||||
}
|
||||
catch (Il2CppException e)
|
||||
{
|
||||
Error(e);
|
||||
DisconnectPopup.Instance.ShowCustom(e.Message[..e.Message.IndexOf("\n--- BEGIN IL2CPP STACK TRACE ---\n", StringComparison.Ordinal)]);
|
||||
MatchMaker.Instance.NotConnecting();
|
||||
yield break;
|
||||
}
|
||||
|
||||
client.Connect(MatchMakerModes.HostAndClient, null);
|
||||
|
||||
yield return client.WaitForConnectionOrFail();
|
||||
|
||||
DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using AmongUs.Data;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using Reactor.Debugger.Window.Tabs;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.ImGui;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger.Window;
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
internal sealed class DebuggerWindow : MonoBehaviour
|
||||
{
|
||||
private readonly DragWindow _window;
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public BaseTab[] Tabs { get; } =
|
||||
{
|
||||
new ConfigTab(),
|
||||
new GameTab(),
|
||||
new AutoJoinTab(),
|
||||
};
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public BaseTab SelectedTab { get; private set; }
|
||||
|
||||
public DebuggerWindow(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
SelectedTab = Tabs[0];
|
||||
|
||||
_window = new DragWindow(new Rect(20, 20, 0, 0), "Reactor.Debugger", () =>
|
||||
{
|
||||
var clientName = DataManager.Player.Customization.Name;
|
||||
if (AmongUsClient.Instance && AmongUsClient.Instance.AmHost) clientName += " (host)";
|
||||
GUILayout.Label("Name: " + clientName);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
foreach (var tab in Tabs)
|
||||
{
|
||||
if (GUILayout.Toggle(tab == SelectedTab, tab.Name, new GUIStyle(GUI.skin.button)))
|
||||
{
|
||||
SelectedTab = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(5f);
|
||||
|
||||
SelectedTab.OnGUI();
|
||||
})
|
||||
{
|
||||
Enabled = false,
|
||||
};
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F1))
|
||||
{
|
||||
_window.Enabled = !_window.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
_window.OnGUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using BepInEx.Unity.IL2CPP.Utils;
|
||||
using Reactor.Debugger.AutoJoin;
|
||||
using Reactor.Debugger.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger.Window.Tabs;
|
||||
|
||||
internal sealed class AutoJoinTab : BaseTab
|
||||
{
|
||||
public override string Name => "AutoJoin";
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (AutoJoinConnectionManager.Server is { } server)
|
||||
{
|
||||
GUILayout.Label($"Hosting ({server.Clients.Count} clients)");
|
||||
|
||||
if (GUILayout.Button("Stop"))
|
||||
{
|
||||
server.Dispose();
|
||||
}
|
||||
|
||||
if (AmongUsClient.Instance)
|
||||
{
|
||||
if (GUILayout.Button("Create an online game"))
|
||||
{
|
||||
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoCreateOnlineGame());
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Create a local game"))
|
||||
{
|
||||
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoCreateLocalGame());
|
||||
}
|
||||
|
||||
if (
|
||||
AmongUsClient.Instance.AmConnected &&
|
||||
AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay &&
|
||||
GUILayout.Button("Join me")
|
||||
)
|
||||
{
|
||||
server.SendJoinMe(AmongUsClient.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AutoJoinConnectionManager.Client is { } client)
|
||||
{
|
||||
GUILayout.Label("Connected");
|
||||
|
||||
if (GUILayout.Button("Disconnect"))
|
||||
{
|
||||
AutoJoinConnectionManager.Client = null;
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Request join"))
|
||||
{
|
||||
client.SendRequestJoin();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Connect"))
|
||||
{
|
||||
AutoJoinConnectionManager.StartOrConnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Reactor.Debugger.Window.Tabs;
|
||||
|
||||
internal abstract class BaseTab
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public abstract void OnGUI();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger.Window.Tabs;
|
||||
|
||||
internal sealed class ConfigTab : BaseTab
|
||||
{
|
||||
public override string Name => "Config";
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
var configs = PluginSingleton<ReactorPlugin>.Instance.Config.Concat(PluginSingleton<DebuggerPlugin>.Instance.Config);
|
||||
|
||||
foreach (var section in configs.GroupBy(e => e.Key.Section))
|
||||
{
|
||||
GUILayout.Label(section.Key);
|
||||
|
||||
foreach (var (definition, entry) in section)
|
||||
{
|
||||
if (entry is ConfigEntry<bool> booleanEntry)
|
||||
{
|
||||
booleanEntry.Value = GUILayout.Toggle(booleanEntry.Value, definition.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Reactor.Debugger.Window.Tabs;
|
||||
|
||||
internal sealed class GameTab : BaseTab
|
||||
{
|
||||
public override string Name => "Game";
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
var localPlayer = PlayerControl.LocalPlayer;
|
||||
if (!localPlayer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var amHost = AmongUsClient.Instance.AmHost;
|
||||
|
||||
var position = localPlayer.transform.position;
|
||||
GUILayout.Label($"Position: ({position.x:F3}, {position.y:F3})");
|
||||
|
||||
var physics = localPlayer.MyPhysics;
|
||||
GUILayout.Label($"Speed: {physics.Speed}");
|
||||
physics.Speed = GUILayout.HorizontalSlider(physics.Speed, 0, 25);
|
||||
|
||||
localPlayer.Collider.enabled = GUILayout.Toggle(localPlayer.Collider.enabled, "Collisions");
|
||||
|
||||
if (ShipStatus.Instance)
|
||||
{
|
||||
if (amHost && GUILayout.Button("Force game end"))
|
||||
{
|
||||
ShipStatus.Instance.enabled = false;
|
||||
GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
|
||||
}
|
||||
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
if (GUILayout.Button("Stop the meeting"))
|
||||
{
|
||||
MeetingHud.Instance.RpcClose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Call a meeting"))
|
||||
{
|
||||
localPlayer.CmdReportDeadBody(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (TutorialManager.InstanceExists)
|
||||
{
|
||||
var newIsImpostor = GUILayout.Toggle(localPlayer.Data.Role.IsImpostor, "Is Impostor");
|
||||
if (localPlayer.Data.Role.IsImpostor != newIsImpostor)
|
||||
{
|
||||
localPlayer.RpcSetRole(newIsImpostor ? RoleTypes.Impostor : RoleTypes.Crewmate);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Spawn a dummy"))
|
||||
{
|
||||
SpawnDummy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SpawnDummy()
|
||||
{
|
||||
var playerControl = Object.Instantiate(TutorialManager.Instance.PlayerPrefab);
|
||||
var playerId = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
|
||||
|
||||
GameData.Instance.AddPlayer(playerControl);
|
||||
AmongUsClient.Instance.Spawn(playerControl);
|
||||
|
||||
playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
|
||||
playerControl.GetComponent<DummyBehaviour>().enabled = true;
|
||||
playerControl.NetTransform.enabled = false;
|
||||
|
||||
playerControl.SetName($"{TranslationController.Instance.GetString(StringNames.Dummy)} {playerId}");
|
||||
|
||||
var color = (byte) (playerId % Palette.PlayerColors.Length);
|
||||
playerControl.SetColor(color);
|
||||
playerControl.SetHat(HatManager.Instance.allHats[playerId % HatManager.Instance.allHats.Count].ProdId, playerControl.Data.DefaultOutfit.ColorId);
|
||||
playerControl.SetPet(HatManager.Instance.allPets[playerId % HatManager.Instance.allPets.Count].ProdId);
|
||||
playerControl.SetSkin(HatManager.Instance.allSkins[playerId % HatManager.Instance.allSkins.Count].ProdId, color);
|
||||
playerControl.SetVisor(HatManager.Instance.allVisors[playerId % HatManager.Instance.allVisors.Count].ProdId, color);
|
||||
playerControl.SetNamePlate(HatManager.Instance.allNamePlates[playerId % HatManager.Instance.allNamePlates.Count].ProdId);
|
||||
playerControl.Data.PlayerLevel = playerId;
|
||||
|
||||
GameData.Instance.RpcSetTasks(playerControl.PlayerId, new Il2CppStructArray<byte>(0));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user