mirror of
https://github.com/D1GQ/BetterAmongUs.git
synced 2026-10-19 15:51:52 +00:00
25 files changed
+775
-537
No files matched your search
@@ -11,6 +11,7 @@ using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Modules.OptionItems;
|
||||
using BetterAmongUs.Modules.Support;
|
||||
using BetterAmongUs.Network;
|
||||
using BetterAmongUs.Patches.Client;
|
||||
using BetterAmongUs.Patches.Gameplay.UI.Settings;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
@@ -302,6 +303,8 @@ internal class BAUPlugin : BasePlugin
|
||||
UnlockFPS, ShowFPS, CommandPrefix,
|
||||
FavoriteColor, SettingsPreset
|
||||
]);
|
||||
|
||||
OptionsMenuBehaviourPatch.UpdateFrameRate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -35,7 +35,7 @@ internal abstract class AbstractJsonFile
|
||||
if (_hasInit) return;
|
||||
_hasInit = true;
|
||||
|
||||
if (!CheckFile())
|
||||
if (!IsFileValid())
|
||||
{
|
||||
Save();
|
||||
return;
|
||||
@@ -106,15 +106,6 @@ internal abstract class AbstractJsonFile
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the content from the JSON file.
|
||||
/// </summary>
|
||||
/// <returns>The content of the JSON file.</returns>
|
||||
protected virtual string ReadFromFile()
|
||||
{
|
||||
return File.ReadAllText(FilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current instance's data to the JSON file.
|
||||
/// </summary>
|
||||
@@ -136,19 +127,10 @@ internal abstract class AbstractJsonFile
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the JSON string to the file.
|
||||
/// Determines whether the file specified by the current FilePath exists and contains valid, non-empty JSON content.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON string to write.</param>
|
||||
protected virtual void WriteToFile(string json)
|
||||
{
|
||||
File.WriteAllText(FilePath, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the JSON file exists and contains valid data.
|
||||
/// </summary>
|
||||
/// <returns>True if the file exists and contains valid JSON data, false otherwise.</returns>
|
||||
private bool CheckFile()
|
||||
/// <returns>true if the file exists, is not empty, and contains valid, non-empty JSON content; otherwise, false.</returns>
|
||||
private bool IsFileValid()
|
||||
{
|
||||
var directory = Path.GetDirectoryName(FilePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
@@ -163,26 +145,23 @@ internal abstract class AbstractJsonFile
|
||||
|
||||
var content = TryReadFromFile();
|
||||
if (string.IsNullOrEmpty(content.Trim())) return false;
|
||||
var jsonElement = JsonSerializer.Deserialize<JsonElement>(content);
|
||||
|
||||
var fileInfo = new FileInfo(FilePath);
|
||||
if (fileInfo.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jsonElement = JsonSerializer.Deserialize<JsonElement>(content);
|
||||
|
||||
var fileInfo = new FileInfo(FilePath);
|
||||
if (fileInfo.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jsonElement.ValueKind == JsonValueKind.Object && !jsonElement.EnumerateObject().Any() ||
|
||||
jsonElement.ValueKind == JsonValueKind.Array && !jsonElement.EnumerateArray().Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
@@ -190,4 +169,22 @@ internal abstract class AbstractJsonFile
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the content from the JSON file.
|
||||
/// </summary>
|
||||
/// <returns>The content of the JSON file.</returns>
|
||||
protected virtual string ReadFromFile()
|
||||
{
|
||||
return File.ReadAllText(FilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the JSON string to the file.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON string to write.</param>
|
||||
protected virtual void WriteToFile(string json)
|
||||
{
|
||||
File.WriteAllText(FilePath, json);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using AmongUs.InnerNet.GameDataMessages;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BetterAmongUs.Helpers;
|
||||
|
||||
@@ -38,255 +37,6 @@ internal static class InnerNetClientHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an array of boolean values to a MessageWriter in a packed format to save space.
|
||||
/// Each byte stores up to 8 boolean values as bits.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="boolsEnumerable">The boolean values to write.</param>
|
||||
internal static void WriteBooleans(this MessageWriter writer, IEnumerable<bool> boolsEnumerable)
|
||||
{
|
||||
bool[] bools = [.. boolsEnumerable];
|
||||
|
||||
writer.Write(bools.Length);
|
||||
|
||||
byte currentByte = 0;
|
||||
int bitIndex = 0;
|
||||
|
||||
foreach (bool b in bools)
|
||||
{
|
||||
if (b) currentByte |= (byte)(1 << bitIndex);
|
||||
|
||||
bitIndex++;
|
||||
|
||||
if (bitIndex == 8)
|
||||
{
|
||||
writer.Write(currentByte);
|
||||
currentByte = 0;
|
||||
bitIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitIndex > 0) writer.Write(currentByte);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an array of boolean values from a MessageReader that were previously packed using WriteBooleans.
|
||||
/// </summary>
|
||||
/// <param name="reader">The MessageReader to read from.</param>
|
||||
/// <returns>An array of boolean values.</returns>
|
||||
internal static bool[] ReadBooleans(this MessageReader reader)
|
||||
{
|
||||
int length = reader.ReadInt32();
|
||||
bool[] bools = new bool[length];
|
||||
|
||||
int bitIndex = 0;
|
||||
byte currentByte = 0;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
|
||||
if (bitIndex == 0) currentByte = reader.ReadByte();
|
||||
|
||||
bools[i] = (currentByte & 1 << bitIndex) != 0;
|
||||
|
||||
bitIndex++;
|
||||
|
||||
if (bitIndex == 8) bitIndex = 0;
|
||||
}
|
||||
|
||||
return bools;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="bytesEnumerable">The byte values to write.</param>
|
||||
internal static void WriteBytes(this MessageWriter writer, IEnumerable<byte> bytesEnumerable)
|
||||
{
|
||||
byte[] bytes = bytesEnumerable.ToArray();
|
||||
|
||||
writer.Write(bytes.Length);
|
||||
writer.Write(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes.
|
||||
/// </summary>
|
||||
/// <param name="reader">The MessageReader to read from.</param>
|
||||
/// <returns>An array of bytes.</returns>
|
||||
internal static byte[] ReadBytes(this MessageReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
var bytes = reader.ReadBytes(count);
|
||||
return [.. bytes];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="bytesEnumerable">The byte values to write.</param>
|
||||
internal static void WritePackedBytes(this MessageWriter writer, IEnumerable<byte> bytesEnumerable)
|
||||
{
|
||||
byte[] bytes = bytesEnumerable.ToArray();
|
||||
|
||||
writer.Write((byte)bytes.Length);
|
||||
|
||||
for (int i = 0; i < bytes.Length; i += 2)
|
||||
{
|
||||
byte packedBytes;
|
||||
if (i + 1 < bytes.Length) packedBytes = (byte)((bytes[i] & 0x0F) << 4 | bytes[i + 1] & 0x0F);
|
||||
else packedBytes = (byte)((bytes[i] & 0x0F) << 4);
|
||||
writer.Write(packedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes.
|
||||
/// </summary>
|
||||
/// <param name="reader">The MessageReader to read from.</param>
|
||||
/// <returns>An array of bytes.</returns>
|
||||
internal static byte[] ReadPackedBytes(this MessageReader reader)
|
||||
{
|
||||
int count = reader.ReadByte();
|
||||
List<byte> bytes = [];
|
||||
|
||||
for (int i = 0; i < (count + 1) / 2; i++)
|
||||
{
|
||||
byte packedBytes = reader.ReadByte();
|
||||
bytes.Add((byte)(packedBytes >> 4 & 0x0F));
|
||||
|
||||
if (bytes.Count < count) bytes.Add((byte)(packedBytes & 0x0F));
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a floating-point value to a MessageWriter in a packed format to save space.
|
||||
/// Handles negative values, integers, and decimal numbers efficiently.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="value">The float value to write.</param>
|
||||
internal static void WritePacked(this MessageWriter writer, float value)
|
||||
{
|
||||
bool isNegative = value < 0;
|
||||
float absValue = Math.Abs(value);
|
||||
|
||||
// Check if it's a solid number (integer)
|
||||
bool isSolidNumber = absValue == (uint)absValue && absValue <= uint.MaxValue;
|
||||
|
||||
// Track the number of decimal places dynamically
|
||||
int decimalPlaces = 0;
|
||||
int scaleFactor = 1;
|
||||
bool needsFallback = false; // Flag to determine if we need to send the value as a regular float
|
||||
|
||||
// Check if the value is not a solid number
|
||||
if (!isSolidNumber)
|
||||
{
|
||||
// Convert to string to count decimals (avoiding floating point precision issues)
|
||||
string absValueStr = absValue.ToString("G", CultureInfo.InvariantCulture);
|
||||
if (absValueStr.Contains('.'))
|
||||
{
|
||||
decimalPlaces = absValueStr.Split('.')[1].Length; // Count decimals after the period
|
||||
scaleFactor = (int)Math.Pow(10, decimalPlaces); // Set scale factor dynamically
|
||||
}
|
||||
}
|
||||
|
||||
// If the value can't be packed efficiently, we need a fallback
|
||||
if (!isSolidNumber || absValue * scaleFactor > uint.MaxValue)
|
||||
{
|
||||
needsFallback = true; // Flag for fallback as a regular float
|
||||
}
|
||||
|
||||
// Pack the flags into a list of booleans
|
||||
var flags = new List<bool>
|
||||
{
|
||||
isNegative,
|
||||
isSolidNumber,
|
||||
decimalPlaces > 0,
|
||||
needsFallback
|
||||
};
|
||||
|
||||
// Write the flags using WriteBooleans
|
||||
writer.WriteBooleans(flags);
|
||||
|
||||
// Write the decimal places as a byte (0-3 for simplicity)
|
||||
writer.Write((byte)decimalPlaces);
|
||||
|
||||
// If it's a solid number, pack as uint
|
||||
if (isSolidNumber && !needsFallback)
|
||||
{
|
||||
writer.Write((uint)absValue);
|
||||
}
|
||||
|
||||
// If it's a decimal number, scale and pack it as uint (but check if it can fit)
|
||||
else if (decimalPlaces > 0 && !needsFallback)
|
||||
{
|
||||
// Check if the scaled value fits into a uint
|
||||
if (absValue * scaleFactor <= uint.MaxValue)
|
||||
{
|
||||
uint scaledValue = (uint)(absValue * scaleFactor); // Dynamically scale the value
|
||||
writer.Write(scaledValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it can't fit into a uint, send as regular float
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send as regular float if it requires fallback
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a floating-point value from a MessageReader that was previously packed using WritePacked.
|
||||
/// Handles negative values, integers, and decimal numbers efficiently.
|
||||
/// </summary>
|
||||
/// <param name="reader">The MessageReader to read from.</param>
|
||||
/// <returns>The unpacked float value.</returns>
|
||||
internal static float ReadPackedSingle(this MessageReader reader)
|
||||
{
|
||||
var flags = reader.ReadBooleans();
|
||||
bool isNegative = flags[0];
|
||||
bool isSolidNumber = flags[1];
|
||||
bool hasDecimalPart = flags[2];
|
||||
bool needsFallback = flags[3];
|
||||
|
||||
byte decimalPlaces = reader.ReadByte(); // Read the decimal places count
|
||||
|
||||
// If the value was packed as a regular float, just read it (don't reapply the sign)
|
||||
if (needsFallback)
|
||||
{
|
||||
return reader.ReadSingle(); // The sign is already handled in the packed value
|
||||
}
|
||||
|
||||
// Handle solid number (uint) case
|
||||
if (isSolidNumber)
|
||||
{
|
||||
uint packedValue = reader.ReadUInt32();
|
||||
return isNegative ? -(float)packedValue : packedValue;
|
||||
}
|
||||
// Handle decimal number (scaled uint)
|
||||
else if (hasDecimalPart)
|
||||
{
|
||||
uint scaledValue = reader.ReadUInt32();
|
||||
float scaleFactor = (float)Math.Pow(10, decimalPlaces); // Use the stored decimal places count
|
||||
return isNegative ? -(scaledValue / scaleFactor) : scaledValue / scaleFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For other cases where it's a normal float
|
||||
float value = reader.ReadSingle();
|
||||
return isNegative ? -value : value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a player's ID to a MessageWriter, using 255 for null players.
|
||||
/// </summary>
|
||||
@@ -343,6 +93,31 @@ internal static class InnerNetClientHelper
|
||||
/// <returns>The Vent or null if not found.</returns>
|
||||
internal static Vent? ReadVentId(this MessageReader reader) => BAUPlugin.AllVents.FirstOrDefault(vent => vent.Id == reader.ReadInt32());
|
||||
|
||||
/// <summary>
|
||||
/// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="bytesEnumerable">The byte values to write.</param>
|
||||
internal static void WriteBytes(this MessageWriter writer, IEnumerable<byte> bytesEnumerable)
|
||||
{
|
||||
byte[] bytes = bytesEnumerable.ToArray();
|
||||
|
||||
writer.Write(bytes.Length);
|
||||
writer.Write(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes.
|
||||
/// </summary>
|
||||
/// <param name="reader">The MessageReader to read from.</param>
|
||||
/// <returns>An array of bytes.</returns>
|
||||
internal static byte[] ReadBytes(this MessageReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
var bytes = reader.ReadBytes(count);
|
||||
return [.. bytes];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a MessageWriter to a MessageReader.
|
||||
/// </summary>
|
||||
@@ -403,90 +178,40 @@ internal static class InnerNetClientHelper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes multiple MessageReaders to a MessageWriter.
|
||||
/// Sends an RPC message immediately to all clients that match the specified target criteria, bypassing standard
|
||||
/// synchronization mechanisms.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to write to.</param>
|
||||
/// <param name="readers">The MessageReaders to write.</param>
|
||||
/// <param name="clear">Whether to clear the writer before writing.</param>
|
||||
/// <returns>The MessageWriter for method chaining.</returns>
|
||||
internal static MessageWriter WriteReaders(this MessageWriter writer, IEnumerable<MessageReader> readers, bool clear = false)
|
||||
/// <param name="client">The InnerNetClient instance used to send the RPC messages.</param>
|
||||
/// <param name="playerNetId">The network ID of the player on whose behalf the RPC is sent.</param>
|
||||
/// <param name="callId">The identifier for the RPC call to be executed.</param>
|
||||
/// <param name="option">The send option that determines how the message is delivered (e.g., reliability, ordering).</param>
|
||||
/// <param name="targetClients">A function that selects which clients should receive the RPC message. The RPC is sent only to clients for which
|
||||
/// this function returns <see langword="true"/>.</param>
|
||||
/// <param name="writeTo">An action that writes the RPC payload to the provided MessageWriter.</param>
|
||||
internal static void SendRpcImmediatelyDesync(this InnerNetClient client, uint playerNetId, RpcCalls callId, SendOption option, Func<ClientData, bool> targetClients, Action<MessageWriter> writeTo)
|
||||
{
|
||||
if (clear) writer.Clear(writer.SendOption);
|
||||
|
||||
foreach (MessageReader reader in readers)
|
||||
foreach (var allClients in AmongUsClient.Instance.allClients)
|
||||
{
|
||||
writer.StartMessage(reader.Tag);
|
||||
writer.Write(reader.ReadBytes(reader.Length));
|
||||
writer.EndMessage();
|
||||
}
|
||||
if (!targetClients(allClients)) continue;
|
||||
|
||||
return writer;
|
||||
client.SendRpcImmediately(playerNetId, callId, option, writeTo, allClients.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of a MessageWriter.
|
||||
/// Sends a remote procedure call (RPC) message immediately to the specified player, using the provided message
|
||||
/// writer action and send option.
|
||||
/// </summary>
|
||||
/// <param name="writer">The MessageWriter to copy.</param>
|
||||
/// <returns>A new MessageWriter with the same content.</returns>
|
||||
internal static MessageWriter Copy(this MessageWriter writer)
|
||||
/// <param name="client">The InnerNetClient instance used to send the RPC message.</param>
|
||||
/// <param name="playerNetId">The network identifier of the target player to whom the RPC is sent.</param>
|
||||
/// <param name="callId">The identifier of the RPC call to invoke.</param>
|
||||
/// <param name="option">The send option that determines how the message is delivered (such as reliability or channel).</param>
|
||||
/// <param name="writeTo">An action that writes the RPC message content to the provided MessageWriter.</param>
|
||||
/// <param name="targetClientId">The client identifier of the specific target client. Use -1 to broadcast to all clients.</param>
|
||||
internal static void SendRpcImmediately(this InnerNetClient client, uint playerNetId, RpcCalls callId, SendOption option, Action<MessageWriter> writeTo, int targetClientId = -1)
|
||||
{
|
||||
var newWriter = MessageWriter.Get(writer.SendOption);
|
||||
newWriter.Write(writer.ToByteArray(false));
|
||||
return newWriter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the RPC desynchronization process for the given player, call ID, and send option.
|
||||
/// </summary>
|
||||
/// <param name="client">The InnerNetClient instance.</param>
|
||||
/// <param name="playerNetId">The network ID of the player.</param>
|
||||
/// <param name="callId">The RPC call ID.</param>
|
||||
/// <param name="option">The send option for the RPC.</param>
|
||||
/// <param name="ignoreClientId">The client ID to ignore. Default is -1, which means no client is ignored.</param>
|
||||
/// <param name="clientCheck">Optional function to filter which clients receive the RPC.</param>
|
||||
/// <returns>A list of MessageWriter instances for the RPC calls.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(PlayerNetId, (byte)RpcCalls, SendOption, ClientId);
|
||||
/// messageWriter.ForEach(mW => mW.Write("RPC TEST"));
|
||||
/// AmongUsClient.Instance.FinishRpcDesync(messageWriter);
|
||||
/// </code>
|
||||
/// </example>
|
||||
internal static List<MessageWriter> StartRpcDesync(this InnerNetClient client, uint playerNetId, byte callId, SendOption option, int ignoreClientId = -1, Func<ClientData, bool>? clientCheck = null)
|
||||
{
|
||||
List<MessageWriter> messageWriters = [];
|
||||
|
||||
if (ignoreClientId < 0)
|
||||
{
|
||||
messageWriters.Add(client.StartRpcImmediately(playerNetId, callId, option, -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var allClients in AmongUsClient.Instance.allClients.WhereIl2Cpp(c => c.Id != ignoreClientId))
|
||||
{
|
||||
if (clientCheck == null || clientCheck.Invoke(allClients))
|
||||
{
|
||||
messageWriters.Add(client.StartRpcImmediately(playerNetId, callId, option, allClients.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messageWriters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes and sends the RPC desynchronization messages.
|
||||
/// </summary>
|
||||
/// <param name="client">The InnerNetClient instance.</param>
|
||||
/// <param name="messageWriters">The list of MessageWriters to finish and send.</param>
|
||||
internal static void FinishRpcDesync(this InnerNetClient client, List<MessageWriter> messageWriters)
|
||||
{
|
||||
foreach (var msg in messageWriters)
|
||||
{
|
||||
msg.EndMessage();
|
||||
msg.EndMessage();
|
||||
client.SendOrDisconnect(msg);
|
||||
msg.Recycle();
|
||||
}
|
||||
var writer = client.StartRpcImmediately(playerNetId, (byte)callId, option, targetClientId);
|
||||
writeTo(writer);
|
||||
client.FinishRpcImmediately(writer);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ static class PlayerControlHelper
|
||||
{
|
||||
var Ban = ban || forceBan;
|
||||
|
||||
if (!GameState.IsHost || player.IsLocalPlayer() || !player.DataIsCollected() && !bypassDataCheck || player.IsHost() || player.isDummy)
|
||||
if (!GameState.IsHost || player.IsLocalPlayer() || (!player.DataIsCollected() && !bypassDataCheck) || player.IsHost() || player.isDummy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ static class PlayerControlHelper
|
||||
/// </summary>
|
||||
/// <param name="data">The player data to check.</param>
|
||||
/// <returns>True if the player is on the impostor team.</returns>
|
||||
internal static bool IsImpostorTeam(this NetworkedPlayerInfo data) => data?.RoleType is RoleTypes.Impostor or RoleTypes.ImpostorGhost or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper;
|
||||
internal static bool IsImpostorTeam(this NetworkedPlayerInfo data) => data?.RoleType.GetBehaviourPrefab().IsImpostor == true;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player is an impostor teammate of the local player.
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace BetterAmongUs.Helpers;
|
||||
/// </summary>
|
||||
internal static class RoleHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a lazily initialized lookup dictionary that maps each role type to its corresponding role behavior.
|
||||
/// </summary>
|
||||
private static readonly Lazy<Dictionary<RoleTypes, RoleBehaviour>> roleLookup =
|
||||
new(() =>
|
||||
{
|
||||
@@ -34,9 +37,17 @@ internal static class RoleHelper
|
||||
/// </summary>
|
||||
/// <param name="role">The role type to check.</param>
|
||||
/// <returns>True if the role is part of the impostor team, false otherwise.</returns>
|
||||
internal static bool IsImpostorRole(RoleTypes role) =>
|
||||
internal static bool IsImpostorRole(this RoleTypes role) =>
|
||||
role.GetBehaviourPrefab().TeamType is RoleTeamTypes.Impostor;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified role is considered a ghost role.
|
||||
/// </summary>
|
||||
/// <param name="role">The role type to check.</param>
|
||||
/// <returns>true if the role is classified as a ghost role; otherwise, false.</returns>
|
||||
internal static bool IsGhostRole(this RoleTypes role) =>
|
||||
RoleManager.IsGhostRole(role);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of a role type.
|
||||
/// </summary>
|
||||
|
||||
@@ -432,6 +432,13 @@ internal static class NetworkManager
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (netObj is CustomNetworkTransform networkTransform)
|
||||
{
|
||||
if (!PlayerRpc(networkTransform.myPlayer, callId, reader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ internal static class ModInfo
|
||||
/// <summary>
|
||||
/// The hotfix number for hotfix releases.
|
||||
/// </summary>
|
||||
internal const string HOTFIX_NUM = "2";
|
||||
internal const string HOTFIX_NUM = "3";
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is a hotfix release.
|
||||
|
||||
@@ -7,9 +7,7 @@ using BetterAmongUs.Modules.Support;
|
||||
using BetterAmongUs.Mono;
|
||||
using BetterAmongUs.Network;
|
||||
using BetterAmongUs.Patches.Gameplay.UI.Settings;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
|
||||
namespace BetterAmongUs.Modules.AntiCheat;
|
||||
|
||||
@@ -60,73 +58,6 @@ internal static class BetterAntiCheat
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Harmony patch for PlatformSpecificData to detect platform spoofing.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(PlatformSpecificData))]
|
||||
class PlatformSpecificDataPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Postfix patch to validate platform data after deserialization.
|
||||
/// </summary>
|
||||
[HarmonyPatch(nameof(PlatformSpecificData.Deserialize))]
|
||||
[HarmonyPostfix]
|
||||
internal static void Deserialize_Postfix(PlatformSpecificData __instance)
|
||||
{
|
||||
if (!BAUPlugin.AntiCheat.Value || BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_Anticheat) || !GameState.IsVanillaServer) return;
|
||||
|
||||
if (GameState.IsLobby)
|
||||
{
|
||||
try
|
||||
{
|
||||
LateTask.Schedule(() =>
|
||||
{
|
||||
var player = BAUPlugin.AllPlayerControls.FirstOrDefault(pc => pc.GetClient().PlatformData == __instance);
|
||||
|
||||
if (player != null && __instance?.Platform != null)
|
||||
{
|
||||
if (__instance.Platform is Platforms.StandaloneWin10 or Platforms.Xbox)
|
||||
{
|
||||
if (__instance.XboxPlatformId.ToString().Length is < 10 or > 16)
|
||||
{
|
||||
player.ReportPlayer(ReportReasons.Cheating_Hacking);
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
Logger_.LogCheat($"{player.BetterData().RealName} {Translator.GetString("AntiCheat.PlatformSpoofer")}: {__instance.XboxPlatformId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (__instance.Platform is Platforms.Playstation)
|
||||
{
|
||||
if (__instance.PsnPlatformId.ToString().Length is < 14 or > 20)
|
||||
{
|
||||
player.ReportPlayer(ReportReasons.Cheating_Hacking);
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
Logger_.LogCheat($"{player.BetterData().RealName} {Translator.GetString("AntiCheat.PlatformSpoofer")}: {__instance.PsnPlatformId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (__instance.Platform is Platforms.Unknown || !Enum.IsDefined(__instance.Platform))
|
||||
{
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}, 3.5f, shouldLog: false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles RPCs before anti-cheat detection checks.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,16 +12,6 @@ internal sealed class SetLevelHandler : RPCHandler
|
||||
{
|
||||
internal override byte CallId => (byte)RpcCalls.SetLevel;
|
||||
|
||||
internal override void Handle(PlayerControl? sender, MessageReader reader)
|
||||
{
|
||||
uint level = reader.ReadPackedUInt32() + 1;
|
||||
|
||||
if (level < BetterGameSettings.KickLevelBelow.GetInt())
|
||||
{
|
||||
sender.Kick(setReasonInfo: $" is level {level}, level must be equal or above {BetterGameSettings.KickLevelBelow.GetInt()} to join");
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool HandleAntiCheatCancel(PlayerControl? sender, MessageReader reader)
|
||||
{
|
||||
if (sender.DataIsCollected() == true && sender.BetterData().AntiCheatInfo.HasSetLevel && !GameState.IsLocalGame && GameState.IsVanillaServer)
|
||||
|
||||
@@ -163,6 +163,18 @@ public static class BAUModdedSupportFlags
|
||||
/// </summary>
|
||||
public static string Disable_EndGameSummary = "gameplay.disable.endgamesummary";
|
||||
|
||||
/// <summary>
|
||||
/// Disables custom icon on mini map.
|
||||
/// When enabled by another mod, custom icons on the mini map will not be generated.
|
||||
/// </summary>
|
||||
public static string Disable_MinimapIcons = "gameplay.disable.minimapicons";
|
||||
|
||||
/// <summary>
|
||||
/// Disables custom vent color highlights.
|
||||
/// When enabled by another mod, vent colors when highlighted will not use vent groups.
|
||||
/// </summary>
|
||||
public static string Disable_VentColorGroups = "gameplay.disable.ventcolorgroups";
|
||||
|
||||
private static readonly HashSet<string> _flags = [];
|
||||
private static bool _initialized = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BetterAmongUs.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality for grouping vents and assigning colors to vent groups.
|
||||
/// </summary>
|
||||
internal static class VentGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary mapping each vent ID to the lowest ID in its group.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<int, int> ventToLowestId = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary mapping group IDs (lowest vent ID in the group) to their assigned colors.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<int, Color> groupColors = [];
|
||||
|
||||
/// <summary>
|
||||
/// Calculates all vent groups by performing a breadth-first search through connected vents.
|
||||
/// </summary>
|
||||
/// <param name="vents">Array of all vents in the scene to process.</param>
|
||||
/// <remarks>
|
||||
/// Groups vents that are connected via Left, Right, or Center references.
|
||||
/// Each group is assigned a color, and the lowest vent ID in the group becomes the group identifier.
|
||||
/// </remarks>
|
||||
internal static void CalculateAllVentGroups(Vent[] vents)
|
||||
{
|
||||
ventToLowestId.Clear();
|
||||
groupColors.Clear();
|
||||
|
||||
HashSet<Vent> unprocessedVents = [.. vents];
|
||||
|
||||
HashSet<int> groupLowestIds = [];
|
||||
Dictionary<int, List<Vent>> groups = [];
|
||||
|
||||
while (unprocessedVents.Count > 0)
|
||||
{
|
||||
Vent startVent = unprocessedVents.First();
|
||||
|
||||
HashSet<Vent> group = [];
|
||||
Queue<Vent> toVisit = new();
|
||||
int lowestId = startVent.Id;
|
||||
|
||||
toVisit.Enqueue(startVent);
|
||||
|
||||
while (toVisit.Count > 0)
|
||||
{
|
||||
Vent current = toVisit.Dequeue();
|
||||
if (group.Contains(current)) continue;
|
||||
|
||||
group.Add(current);
|
||||
unprocessedVents.Remove(current);
|
||||
|
||||
if (current.Id < lowestId)
|
||||
{
|
||||
lowestId = current.Id;
|
||||
}
|
||||
|
||||
Vent[] connections = { current.Left, current.Right, current.Center };
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (connection != null && !group.Contains(connection) && !toVisit.Contains(connection))
|
||||
{
|
||||
toVisit.Enqueue(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupLowestIds.Add(lowestId);
|
||||
groups[lowestId] = [.. group];
|
||||
}
|
||||
|
||||
Color[] colorGroups =
|
||||
[
|
||||
Color.red,
|
||||
Color.green,
|
||||
Color.blue,
|
||||
Color.gray,
|
||||
Color.magenta,
|
||||
Color.cyan,
|
||||
Color.white
|
||||
];
|
||||
|
||||
for (int i = 0; i < groupLowestIds.Count; i++)
|
||||
{
|
||||
int lowestId = groupLowestIds.ElementAt(i);
|
||||
int colorIndex = i % colorGroups.Length;
|
||||
groupColors[lowestId] = colorGroups[colorIndex] + (new Color(1f, 1f, 1f, 0f) * 0.2f);
|
||||
}
|
||||
|
||||
foreach (var kvp in groups)
|
||||
{
|
||||
int lowestId = kvp.Key;
|
||||
foreach (var vent in kvp.Value)
|
||||
{
|
||||
ventToLowestId[vent.Id] = lowestId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group color for a specific vent.
|
||||
/// </summary>
|
||||
/// <param name="vent">The vent to get the group color for.</param>
|
||||
/// <returns>
|
||||
/// The color assigned to the vent's group, or <see cref="Color.black"/> if the vent hasn't been processed.
|
||||
/// </returns>
|
||||
internal static Color GetVentGroupColor(Vent vent)
|
||||
{
|
||||
if (ventToLowestId.TryGetValue(vent.Id, out int lowestId))
|
||||
{
|
||||
return groupColors[lowestId];
|
||||
}
|
||||
|
||||
return Color.black;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BetterAmongUs.Mono;
|
||||
|
||||
/// <summary>
|
||||
/// Animates a map icon by smoothly scaling it up and down when conditions are met.
|
||||
/// </summary>
|
||||
internal class AnimatedMapIcon : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that determines whether the icon should animate.
|
||||
/// Returns true if animation should occur, false otherwise.
|
||||
/// </summary>
|
||||
internal event Func<bool>? ShouldAnimate;
|
||||
|
||||
private Vector3 originalScale;
|
||||
|
||||
/// <summary>
|
||||
/// The multiplier applied to the original scale when fully animated.
|
||||
/// </summary>
|
||||
private readonly float scaleMultiplier = 1.2f;
|
||||
|
||||
/// <summary>
|
||||
/// The speed of the animation loop.
|
||||
/// </summary>
|
||||
private readonly float animationSpeed = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the component by storing the original scale of the icon.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
originalScale = transform.localScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the icon scale each frame based on the animation state.
|
||||
/// When ShouldAnimate returns true, creates a smooth ping-pong scaling effect.
|
||||
/// When ShouldAnimate returns false, resets to the original scale.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (ShouldAnimate?.Invoke() == true)
|
||||
{
|
||||
float pingPong = Mathf.PingPong(Time.time * animationSpeed, 1f);
|
||||
float t = Mathf.SmoothStep(0f, 1f, pingPong);
|
||||
Vector3 targetScale = originalScale * (1f + (scaleMultiplier - 1f) * t);
|
||||
|
||||
transform.localScale = targetScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.localScale = originalScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -29,15 +29,15 @@ internal static class RPC
|
||||
/// <param name="targetClientId">The specific client ID to target, or -1 to broadcast to all clients.</param>
|
||||
internal static void SendCustomRpcPacked(CustomRPC customRPC, Action<MessageWriter> action, int targetClientId = -1)
|
||||
{
|
||||
var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.SetNamePlateStr, SendOption.Reliable, targetClientId);
|
||||
AmongUsClient.Instance.SendRpcImmediately(PlayerControl.LocalPlayer.NetId, RpcCalls.SetNamePlateStr, SendOption.Reliable, writer =>
|
||||
{
|
||||
writer.Write(DataManager.Player.Customization.NamePlate);
|
||||
writer.Write(PlayerControl.LocalPlayer.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr));
|
||||
|
||||
writer.Write(DataManager.Player.Customization.NamePlate);
|
||||
writer.Write(PlayerControl.LocalPlayer.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr));
|
||||
|
||||
writer.Write(CUSTOM_RPC_FLAG); // Flag to check if its a rpc packed into SetNamePlateStr
|
||||
writer.Write((byte)customRPC);
|
||||
action(writer);
|
||||
AmongUsClient.Instance.FinishRpcImmediately(writer);
|
||||
writer.Write(CUSTOM_RPC_FLAG); // Flag to check if its a rpc packed into SetNamePlateStr
|
||||
writer.Write((byte)customRPC);
|
||||
action(writer);
|
||||
}, targetClientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -113,10 +113,10 @@ internal static class OptionsMenuBehaviourPatch
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateFrameRate()
|
||||
internal static void UpdateFrameRate()
|
||||
{
|
||||
// Toggle between 60 FPS (default) and 165 FPS
|
||||
Application.targetFrameRate = BAUPlugin.UnlockFPS.Value ? 165 : 60;
|
||||
Application.targetFrameRate = BAUPlugin.UnlockFPS.Value ? 999 : 60;
|
||||
}
|
||||
|
||||
private static void OpenSaveData()
|
||||
|
||||
@@ -7,10 +7,14 @@ namespace BetterAmongUs.Patches.Client;
|
||||
[HarmonyPatch]
|
||||
internal static class SplashIntroPatch
|
||||
{
|
||||
private const float MinIntroDuration = 3f;
|
||||
private const float AudioDestroyTime = 1.8f;
|
||||
|
||||
internal static bool Skip = false;
|
||||
internal static bool BetterIntro = false;
|
||||
internal static bool IsReallyDoneLoading = false;
|
||||
private static GameObject? BetterLogo;
|
||||
|
||||
private static GameObject? _betterLogo;
|
||||
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Start))]
|
||||
[HarmonyPrefix]
|
||||
@@ -22,84 +26,112 @@ internal static class SplashIntroPatch
|
||||
IsReallyDoneLoading = false;
|
||||
|
||||
// Hide black overlay by moving it out of view
|
||||
__instance.logoAnimFinish.transform.Find("BlackOverlay").transform.SetLocalY(100f);
|
||||
__instance.logoAnimFinish.transform
|
||||
.Find("BlackOverlay").transform
|
||||
.SetLocalY(100f);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))]
|
||||
[HarmonyPrefix]
|
||||
private static bool SplashManager_Update_Prefix(SplashManager __instance)
|
||||
{
|
||||
// If skip is triggered, just check if loading is done
|
||||
if (Skip)
|
||||
{
|
||||
CheckIfDone(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
// After 1.8 seconds in BAU intro, remove audio to prevent overlap
|
||||
if (Time.time - __instance.startTime > 1.8f && BetterIntro)
|
||||
HandleAudioDestruction(__instance);
|
||||
|
||||
if (TryHandleSkipClick(__instance))
|
||||
{
|
||||
Skip = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CanStartBetterIntro(__instance))
|
||||
{
|
||||
StartBetterIntro(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckIfDone(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void HandleAudioDestruction(SplashManager __instance)
|
||||
{
|
||||
if (!BetterIntro) return;
|
||||
|
||||
if (Time.time - __instance.startTime > AudioDestroyTime)
|
||||
{
|
||||
UnityEngine.Object.Destroy(__instance.logoAnimFinish.GetComponent<AudioSource>());
|
||||
}
|
||||
}
|
||||
|
||||
// Allow mouse click to skip splash screen
|
||||
if (Input.GetKeyDown(KeyCode.Mouse0) || Input.GetKeyDown(KeyCode.Mouse1))
|
||||
{
|
||||
if (CheckIfDone(__instance, true))
|
||||
{
|
||||
Skip = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private static bool TryHandleSkipClick(SplashManager __instance)
|
||||
{
|
||||
if (!Input.GetKeyDown(KeyCode.Mouse0) && !Input.GetKeyDown(KeyCode.Mouse1))
|
||||
return false;
|
||||
|
||||
// When game data is loaded and minimum time has passed
|
||||
if (__instance.doneLoadingRefdata && !__instance.startedSceneLoad && Time.time - __instance.startTime > __instance.minimumSecondsBeforeSceneChange)
|
||||
{
|
||||
if (!BetterIntro)
|
||||
{
|
||||
// Start BAU custom intro sequence
|
||||
__instance.startTime = Time.time;
|
||||
__instance.logoAnimFinish.gameObject.SetActive(false);
|
||||
__instance.logoAnimFinish.gameObject.SetActive(true);
|
||||
return CheckIfDone(__instance, true);
|
||||
}
|
||||
|
||||
// Replace InnerSloth logo with BAU logo
|
||||
GameObject InnerLogo = __instance.logoAnimFinish.transform.Find("LogoRoot/ISLogo").gameObject;
|
||||
BetterLogo = UnityEngine.Object.Instantiate(InnerLogo, InnerLogo.transform.parent);
|
||||
InnerLogo.DestroyObj();
|
||||
BetterLogo.name = "BetterLogo";
|
||||
BetterLogo.GetComponent<SpriteRenderer>().sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Logo.png", 150f);
|
||||
private static bool CanStartBetterIntro(SplashManager __instance)
|
||||
{
|
||||
return __instance.doneLoadingRefdata &&
|
||||
!__instance.startedSceneLoad &&
|
||||
Time.time - __instance.startTime > __instance.minimumSecondsBeforeSceneChange;
|
||||
}
|
||||
|
||||
// Show black overlay
|
||||
__instance.logoAnimFinish.transform.Find("BlackOverlay").transform.SetLocalY(0f);
|
||||
private static void StartBetterIntro(SplashManager __instance)
|
||||
{
|
||||
if (BetterIntro) return;
|
||||
|
||||
BetterIntro = true;
|
||||
return false;
|
||||
}
|
||||
// Start BAU custom intro sequence
|
||||
__instance.startTime = Time.time;
|
||||
__instance.logoAnimFinish.gameObject.SetActive(false);
|
||||
__instance.logoAnimFinish.gameObject.SetActive(true);
|
||||
|
||||
// Check if BAU intro has completed
|
||||
CheckIfDone(__instance);
|
||||
}
|
||||
// Replace InnerSloth logo with BAU logo
|
||||
ReplaceLogo(__instance);
|
||||
|
||||
// Return false to prevent original Update from running (we handle everything)
|
||||
return false;
|
||||
// Show black overlay
|
||||
__instance.logoAnimFinish.transform
|
||||
.Find("BlackOverlay").transform
|
||||
.SetLocalY(0f);
|
||||
|
||||
BetterIntro = true;
|
||||
}
|
||||
|
||||
private static void ReplaceLogo(SplashManager __instance)
|
||||
{
|
||||
var innerLogo = __instance.logoAnimFinish.transform
|
||||
.Find("LogoRoot/ISLogo").gameObject;
|
||||
|
||||
_betterLogo = UnityEngine.Object.Instantiate(innerLogo, innerLogo.transform.parent);
|
||||
innerLogo.DestroyObj();
|
||||
|
||||
_betterLogo.name = "BetterLogo";
|
||||
_betterLogo.GetComponent<SpriteRenderer>().sprite =
|
||||
Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Logo.png", 150f);
|
||||
}
|
||||
|
||||
private static bool CheckIfDone(SplashManager __instance, bool isSkip = false)
|
||||
{
|
||||
// Allow transition if:
|
||||
// 1. BAU intro played for 2 seconds OR
|
||||
// 2. User skipped and BAU intro was shown
|
||||
if ((Time.time - __instance.startTime > 2f && BetterIntro) || (isSkip && BetterIntro))
|
||||
{
|
||||
IsReallyDoneLoading = true;
|
||||
// Allow transition if intro played for minimum duration or user skipped
|
||||
var introComplete = (Time.time - __instance.startTime > MinIntroDuration && BetterIntro) ||
|
||||
(isSkip && BetterIntro);
|
||||
|
||||
// Allow scene transition to proceed
|
||||
__instance.sceneChanger.AllowFinishLoadingScene();
|
||||
__instance.startedSceneLoad = true;
|
||||
__instance.loadingObject.SetActive(true);
|
||||
return true;
|
||||
}
|
||||
if (!introComplete) return false;
|
||||
|
||||
return false;
|
||||
IsReallyDoneLoading = true;
|
||||
|
||||
// Allow scene transition to proceed
|
||||
__instance.sceneChanger.AllowFinishLoadingScene();
|
||||
__instance.startedSceneLoad = true;
|
||||
__instance.loadingObject.SetActive(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using BetterAmongUs.Helpers;
|
||||
using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Patches.Gameplay.UI.Settings;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.Anticheat;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal class CheckPlayerLevelPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
|
||||
[HarmonyPostfix]
|
||||
private static void PlayerControl_FixedUpdate_Postfix(PlayerControl __instance)
|
||||
{
|
||||
if (GameState.IsHost)
|
||||
{
|
||||
if (!__instance.IsLocalPlayer() && (__instance.Data.PlayerLevel < BetterGameSettings.KickLevelBelow.GetInt()))
|
||||
{
|
||||
__instance.Kick(setReasonInfo: $" is level {__instance.Data.PlayerLevel}, level must be equal or above {BetterGameSettings.KickLevelBelow.GetInt()} to join");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using BetterAmongUs.Helpers;
|
||||
using BetterAmongUs.Managers;
|
||||
using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Modules.Support;
|
||||
using BetterAmongUs.Mono;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.Anticheat;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal class PlatformSpoofPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(PlatformSpecificData), nameof(PlatformSpecificData.Deserialize))]
|
||||
[HarmonyPostfix]
|
||||
internal static void PlatformSpecificData_Deserialize_Postfix(PlatformSpecificData __instance)
|
||||
{
|
||||
if (!BAUPlugin.AntiCheat.Value || BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_Anticheat) || !GameState.IsVanillaServer) return;
|
||||
|
||||
if (GameState.IsLobby)
|
||||
{
|
||||
try
|
||||
{
|
||||
LateTask.Schedule(() =>
|
||||
{
|
||||
var player = BAUPlugin.AllPlayerControls.FirstOrDefault(pc => pc.GetClient().PlatformData == __instance);
|
||||
|
||||
if (player != null && __instance?.Platform != null)
|
||||
{
|
||||
if (__instance.Platform is Platforms.StandaloneWin10 or Platforms.Xbox)
|
||||
{
|
||||
if (__instance.XboxPlatformId.ToString().Length is < 10 or > 16)
|
||||
{
|
||||
player.ReportPlayer(ReportReasons.Cheating_Hacking);
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
Logger_.LogCheat($"{player.BetterData().RealName} {Translator.GetString("AntiCheat.PlatformSpoofer")}: {__instance.XboxPlatformId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (__instance.Platform is Platforms.Playstation)
|
||||
{
|
||||
if (__instance.PsnPlatformId.ToString().Length is < 14 or > 20)
|
||||
{
|
||||
player.ReportPlayer(ReportReasons.Cheating_Hacking);
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
Logger_.LogCheat($"{player.BetterData().RealName} {Translator.GetString("AntiCheat.PlatformSpoofer")}: {__instance.PsnPlatformId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (__instance.Platform is Platforms.Unknown || !Enum.IsDefined(__instance.Platform))
|
||||
{
|
||||
BetterNotificationManager.NotifyCheat(player,
|
||||
Translator.GetString("AntiCheat.Reason.PlatformSpoofer"),
|
||||
Translator.GetString("AntiCheat.HasBeenDetectedWithCheat")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}, 3.5f, shouldLog: false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Modules.Support;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.Systems;
|
||||
namespace BetterAmongUs.Patches.Gameplay.Anticheat;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class VoteBanSystemPatch
|
||||
@@ -15,20 +15,23 @@ internal static class RoleManagerPatch
|
||||
internal static Dictionary<string, int> ImpostorMultiplier = []; // HashPuid, Multiplier
|
||||
private static readonly Random random = new();
|
||||
|
||||
// Check if client is not verified Better Among Us user
|
||||
static readonly Func<InnerNet.ClientData, bool> clientCheck = (clientData) =>
|
||||
// Check if client is verified Better Among Us user
|
||||
private static Func<InnerNet.ClientData, bool> SendTo(PlayerControl target)
|
||||
{
|
||||
return clientData?.BetterData()?.IsVerifiedBetterUser != true;
|
||||
};
|
||||
return (clientData) =>
|
||||
{
|
||||
return clientData.Id != target.GetClientId() && clientData?.BetterData()?.IsVerifiedBetterUser != true;
|
||||
};
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SetRole))]
|
||||
[HarmonyPrefix]
|
||||
private static void RoleManager_SetRole_Prefix(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
|
||||
{
|
||||
// Store the original role when player dies (for ghost role purposes)
|
||||
if (RoleManager.IsGhostRole(roleType))
|
||||
if (roleType.IsGhostRole())
|
||||
{
|
||||
if (!RoleManager.IsGhostRole(targetPlayer.Data.RoleType))
|
||||
if (!targetPlayer.Data.RoleType.IsGhostRole())
|
||||
{
|
||||
targetPlayer.BetterData().RoleInfo.DeadDisplayRole = targetPlayer.Data.RoleType;
|
||||
}
|
||||
@@ -113,7 +116,7 @@ internal static class RoleManagerPatch
|
||||
// Get role counts from game options
|
||||
foreach (RoleTypes role in Roles)
|
||||
{
|
||||
if (role.GetBehaviourPrefab().IsImpostor)
|
||||
if (role.IsImpostorRole())
|
||||
ImpostorRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
|
||||
else
|
||||
CrewmateRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
|
||||
@@ -154,12 +157,13 @@ internal static class RoleManagerPatch
|
||||
// Desync role to hide special role from non-BAU players
|
||||
if (BetterGameSettings.DesyncRoles.GetBool())
|
||||
{
|
||||
if (kvp.Key is not RoleTypes.Phantom or RoleTypes.Viper)
|
||||
if (kvp.Key is not (RoleTypes.Phantom or RoleTypes.Viper))
|
||||
{
|
||||
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(pc.NetId, (byte)RpcCalls.SetRole, SendOption.None, pc.GetClientId(), clientCheck);
|
||||
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Impostor));
|
||||
messageWriter.ForEach(mW => mW.Write(false));
|
||||
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
|
||||
AmongUsClient.Instance.SendRpcImmediatelyDesync(pc.NetId, RpcCalls.SetRole, SendOption.None, SendTo(pc), writer =>
|
||||
{
|
||||
writer.Write((ushort)RoleTypes.Impostor);
|
||||
writer.Write(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,10 +199,11 @@ internal static class RoleManagerPatch
|
||||
{
|
||||
if (kvp.Key is not RoleTypes.Noisemaker)
|
||||
{
|
||||
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(pc.NetId, (byte)RpcCalls.SetRole, SendOption.None, pc.GetClientId(), clientCheck);
|
||||
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Crewmate));
|
||||
messageWriter.ForEach(mW => mW.Write(false));
|
||||
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
|
||||
AmongUsClient.Instance.SendRpcImmediatelyDesync(pc.NetId, RpcCalls.SetRole, SendOption.None, SendTo(pc), writer =>
|
||||
{
|
||||
writer.Write((ushort)RoleTypes.Crewmate);
|
||||
writer.Write(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,10 +357,11 @@ internal static class RoleManagerPatch
|
||||
// Desync ghost role to hide it from non-BAU players
|
||||
if (BetterGameSettings.DesyncRoles.GetBool())
|
||||
{
|
||||
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, player.GetClientId(), clientCheck);
|
||||
messageWriter.ForEach(mW => mW.Write((ushort)player.Data.Role.DefaultGhostRole));
|
||||
messageWriter.ForEach(mW => mW.Write(false));
|
||||
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
|
||||
AmongUsClient.Instance.SendRpcImmediatelyDesync(player.NetId, RpcCalls.SetRole, SendOption.None, SendTo(player), writer =>
|
||||
{
|
||||
writer.Write((ushort)player.Data.Role.DefaultGhostRole);
|
||||
writer.Write(false);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using BetterAmongUs.Modules;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.Ship;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class ShipStatusPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake))]
|
||||
[HarmonyPostfix]
|
||||
private static void ShipStatus_Awake_Postfix(ShipStatus __instance)
|
||||
{
|
||||
VentGroups.CalculateAllVentGroups(__instance.AllVents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Modules.Support;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.Ship;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class VentPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))]
|
||||
[HarmonyPrefix]
|
||||
private static bool Vent_SetOutline_Prefix(Vent __instance, bool on, bool mainTarget)
|
||||
{
|
||||
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_VentColorGroups)) return true;
|
||||
|
||||
Color color = VentGroups.GetVentGroupColor(__instance);
|
||||
__instance.myRend.material.SetFloat("_Outline", on ? 1f : 0f);
|
||||
__instance.myRend.material.SetColor("_OutlineColor", color);
|
||||
__instance.myRend.material.SetColor("_AddColor", mainTarget ? color : Color.clear);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using HarmonyLib;
|
||||
using BetterAmongUs.Helpers;
|
||||
using BetterAmongUs.Modules;
|
||||
using BetterAmongUs.Modules.Support;
|
||||
using BetterAmongUs.Mono;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BetterAmongUs.Patches.Gameplay.UI;
|
||||
@@ -6,6 +10,10 @@ namespace BetterAmongUs.Patches.Gameplay.UI;
|
||||
[HarmonyPatch]
|
||||
internal static class MiniMapBehaviourPatch
|
||||
{
|
||||
private const float VentLayerOffset = -0.1f;
|
||||
private const float VentArrowLayerOffset = -0.1f; // VentLayerOffset + VentArrowLayerOffset = -0.2
|
||||
private const float UsableLayerOffset = -0.3f;
|
||||
|
||||
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.ShowNormalMap))]
|
||||
[HarmonyPostfix]
|
||||
private static void MapBehaviour_ShowNormalMap_Postfix(MapBehaviour __instance)
|
||||
@@ -21,13 +29,224 @@ internal static class MiniMapBehaviourPatch
|
||||
private static void MapBehaviour_ShowSabotageMap_Postfix(MapBehaviour __instance)
|
||||
=> __instance.ColorControl.SetColor(new Color(1f, 0.3f, 0f, 1f));
|
||||
|
||||
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.ShowCountOverlay))]
|
||||
[HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.OnEnable))]
|
||||
[HarmonyPostfix]
|
||||
private static void MapBehaviour_ShowCountOverlay_Postfix(MapBehaviour __instance)
|
||||
=> __instance.ColorControl.SetColor(new Color(0.2f, 0.5f, 0f, 1f));
|
||||
private static void MapCountOverlay_OnEnable_Postfix(MapCountOverlay __instance)
|
||||
=> __instance.BackgroundColor.SetColor(PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer) ? Palette.DisabledGrey : new Color(0.2f, 0.5f, 0f, 1f));
|
||||
|
||||
[HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
|
||||
[HarmonyPrefix]
|
||||
private static void MapCountOverlay_Update_Prefix(MapCountOverlay __instance)
|
||||
{
|
||||
if (PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer))
|
||||
{
|
||||
__instance.isSab = true;
|
||||
__instance.BackgroundColor.SetColor(Palette.DisabledGrey);
|
||||
__instance.SabotageText.gameObject.SetActive(true);
|
||||
}
|
||||
else if (!PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer))
|
||||
{
|
||||
__instance.isSab = false;
|
||||
__instance.BackgroundColor.SetColor(new Color(0.2f, 0.5f, 0f, 1f));
|
||||
__instance.SabotageText.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MapConsole), nameof(MapConsole.Use))]
|
||||
[HarmonyPostfix]
|
||||
private static void MapConsole_ShowCountOverlay_Postfix()
|
||||
=> MapBehaviour.Instance.ColorControl.SetColor(new Color(0.2f, 0.5f, 0f, 1f));
|
||||
|
||||
private static Transform? _icons;
|
||||
|
||||
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))]
|
||||
[HarmonyPostfix]
|
||||
private static void MapBehaviour_Show_Postfix(MapBehaviour __instance)
|
||||
{
|
||||
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_MinimapIcons)) return;
|
||||
|
||||
foreach (var button in __instance.infectedOverlay.allButtons)
|
||||
{
|
||||
button.spriteRenderer.color = new Color(1f, 1f, 1f, 0.8f);
|
||||
button.transform.localScale = new Vector3(0.6f, 0.6f, 1f);
|
||||
}
|
||||
|
||||
if (_icons == null)
|
||||
{
|
||||
var icons = new GameObject("Icons")
|
||||
{
|
||||
layer = LayerMask.NameToLayer("UI")
|
||||
};
|
||||
icons.transform.SetParent(__instance.transform.Find("HereIndicatorParent"));
|
||||
icons.transform.localPosition = Vector3.zero;
|
||||
icons.transform.localScale = Vector3.one;
|
||||
_icons = icons.transform;
|
||||
|
||||
foreach (var vent in BAUPlugin.AllVents)
|
||||
{
|
||||
CreateVentIcon(vent);
|
||||
}
|
||||
|
||||
foreach (var map in UnityEngine.Object.FindObjectsOfType<MapConsole>())
|
||||
{
|
||||
CreateUsableIcon(map.Cast<IUsable>());
|
||||
}
|
||||
|
||||
foreach (var system in UnityEngine.Object.FindObjectsOfType<SystemConsole>())
|
||||
{
|
||||
CreateSystemConsoleIcon(system);
|
||||
}
|
||||
|
||||
foreach (var zipline in UnityEngine.Object.FindObjectsOfType<ZiplineConsole>())
|
||||
{
|
||||
var icon = CreateIcon(zipline.image.sprite, "SystemIcon");
|
||||
icon.color = Color.white * 0.7f;
|
||||
icon.transform.localScale = Vector3.one * 0.25f;
|
||||
SetPosFromShip(zipline.transform.position, icon.transform, new Vector3(0f, 0.16f, UsableLayerOffset));
|
||||
}
|
||||
|
||||
foreach (var console in UnityEngine.Object.FindObjectsOfType<Console>())
|
||||
{
|
||||
foreach (var taskType in console.TaskTypes)
|
||||
{
|
||||
if (taskType is TaskTypes.FixLights or TaskTypes.FixComms or TaskTypes.RestoreOxy or TaskTypes.ResetReactor or TaskTypes.ResetSeismic)
|
||||
{
|
||||
var icon = CreateIcon(console.Image.sprite, "ConsoleIcon");
|
||||
icon.color = Color.white * 0.7f;
|
||||
icon.transform.localScale = Vector3.one * 0.5f;
|
||||
SetPosFromShip(console.transform.position, icon.transform, new Vector3(0f, 0f, UsableLayerOffset));
|
||||
var animatedMapIcon = icon.gameObject.AddComponent<AnimatedMapIcon>();
|
||||
animatedMapIcon.ShouldAnimate += () =>
|
||||
{
|
||||
return console.FindTask(PlayerControl.LocalPlayer) != null;
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateVentIcon(Vent vent)
|
||||
{
|
||||
var icon = CreateIcon(Utils.LoadSprite("BetterAmongUs.Resources.Images.Icons.Vent.png", 380), "VentIcon");
|
||||
var color = VentGroups.GetVentGroupColor(vent);
|
||||
icon.color = new Color(color.r, color.g, color.b, 0.7f);
|
||||
|
||||
SetPosFromShip(vent.transform.position, icon.transform, new Vector3(0f, 0f, VentLayerOffset));
|
||||
|
||||
Vent[] nearbyVents = [vent.Left, vent.Right, vent.Center];
|
||||
float maxSpreadShift = 0.5f;
|
||||
float minSpreadShift = 0.2f;
|
||||
float closeDistance = 10f;
|
||||
|
||||
for (int i = 0; i < nearbyVents.Length; i++)
|
||||
{
|
||||
Vent neighborVent = nearbyVents[i];
|
||||
if (neighborVent)
|
||||
{
|
||||
var arrowIcon = CreateIcon(Utils.LoadSprite("BetterAmongUs.Resources.Images.Icons.Arrow.png", 600), "VentArrowIcon");
|
||||
arrowIcon.color = VentGroups.GetVentGroupColor(neighborVent);
|
||||
arrowIcon.transform.SetParent(icon.transform);
|
||||
|
||||
Vector3 directionToNeighbor = neighborVent.transform.position - vent.transform.position;
|
||||
float distanceToNeighbor = directionToNeighbor.magnitude;
|
||||
|
||||
float spreadShift;
|
||||
if (distanceToNeighbor < closeDistance)
|
||||
{
|
||||
float t = distanceToNeighbor / closeDistance;
|
||||
spreadShift = Mathf.Lerp(minSpreadShift, maxSpreadShift, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
spreadShift = maxSpreadShift;
|
||||
}
|
||||
|
||||
Vector3 arrowOffset = directionToNeighbor.normalized * (0.7f + spreadShift);
|
||||
arrowOffset.y -= 0.08f;
|
||||
|
||||
SetPosFromShip(arrowOffset, arrowIcon.transform, new Vector3(0f, 0f, VentArrowLayerOffset));
|
||||
|
||||
Vector3 ventMapPos = vent.transform.position / ShipStatus.Instance.MapScale;
|
||||
ventMapPos.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x);
|
||||
|
||||
Vector3 neighborMapPos = neighborVent.transform.position / ShipStatus.Instance.MapScale;
|
||||
neighborMapPos.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x);
|
||||
|
||||
Vector3 mapDirection = neighborMapPos - ventMapPos;
|
||||
|
||||
float angle = Mathf.Atan2(mapDirection.y, mapDirection.x) * Mathf.Rad2Deg;
|
||||
arrowIcon.transform.rotation = Quaternion.Euler(0, 0, angle - 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateSystemConsoleIcon(SystemConsole systemConsole)
|
||||
{
|
||||
if (systemConsole.UseIcon is not ImageNames.UseButton)
|
||||
{
|
||||
CreateUsableIcon(systemConsole.Cast<IUsable>());
|
||||
}
|
||||
else
|
||||
{
|
||||
var icon = CreateIcon(systemConsole.Image.sprite, "SystemIcon");
|
||||
icon.color = Color.white * 0.7f;
|
||||
icon.transform.localScale = Vector3.one * 0.4f;
|
||||
SetPosFromShip(systemConsole.transform.position, icon.transform, new Vector3(0f, 0f, UsableLayerOffset));
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateUsableIcon(IUsable usable)
|
||||
{
|
||||
if (usable.TryCast<SystemConsole>(out var systemConsole))
|
||||
{
|
||||
if (systemConsole.MinigamePrefab.name == "EmergencyMinigame")
|
||||
{
|
||||
var icon = CreateIcon(Utils.LoadSprite("BetterAmongUs.Resources.Images.Icons.Meeting.png", 500), "MeetingIcon");
|
||||
icon.color = new Color(1f, 1f, 1f, 0.7f);
|
||||
icon.transform.localScale = Vector3.one * 0.35f;
|
||||
SetPosFromShip(usable.Cast<MonoBehaviour>().transform.position, icon.transform, new Vector3(0f, 0f, UsableLayerOffset));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (systemConsole.UseIcon is ImageNames.UseButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (HudManager.Instance.UseButton.fastUseSettings.TryGetValue(usable.UseIcon, out var settings))
|
||||
{
|
||||
var icon = CreateIcon(settings.Image, "UsableIcon");
|
||||
icon.color = new Color(1f, 1f, 1f, 0.7f);
|
||||
icon.transform.localScale = Vector3.one * 0.35f;
|
||||
SetPosFromShip(usable.Cast<MonoBehaviour>().transform.position, icon.transform, new Vector3(0f, 0f, UsableLayerOffset));
|
||||
}
|
||||
}
|
||||
|
||||
private static SpriteRenderer CreateIcon(Sprite sprite, string name = "Icon")
|
||||
{
|
||||
var go = new GameObject(name)
|
||||
{
|
||||
layer = LayerMask.NameToLayer("UI")
|
||||
};
|
||||
go.transform.SetParent(_icons);
|
||||
go.transform.localScale = Vector3.one;
|
||||
var spriteRenderer = go.AddComponent<SpriteRenderer>();
|
||||
spriteRenderer.sprite = sprite;
|
||||
return spriteRenderer;
|
||||
}
|
||||
|
||||
private static void SetPosFromShip(Vector3 shipPos, Transform mapTransform, Vector3? offset = null)
|
||||
{
|
||||
offset ??= Vector3.zero;
|
||||
Vector3 vector = shipPos;
|
||||
vector /= ShipStatus.Instance.MapScale;
|
||||
vector.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x);
|
||||
vector.z = -1f;
|
||||
mapTransform.transform.localPosition = new Vector3(vector.x + offset.Value.x, vector.y + offset.Value.y, offset.Value.z);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace BetterAmongUs.Patches.Gameplay;
|
||||
internal class ZoomPatch
|
||||
{
|
||||
private static bool _wasZooming = false;
|
||||
private static bool _shouldResetButtons = false;
|
||||
private static float _lastOrthographicSize = 0f;
|
||||
|
||||
[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
|
||||
[HarmonyPostfix]
|
||||
@@ -22,6 +22,8 @@ internal class ZoomPatch
|
||||
|
||||
bool shouldReset = !canZoom && _wasZooming;
|
||||
|
||||
_lastOrthographicSize = Camera.main.orthographicSize;
|
||||
|
||||
if (shouldReset)
|
||||
{
|
||||
SetZoomSize(reset: true);
|
||||
@@ -29,19 +31,16 @@ internal class ZoomPatch
|
||||
}
|
||||
else if (canZoom)
|
||||
{
|
||||
_wasZooming = true;
|
||||
|
||||
if (Camera.main.orthographicSize > 3.0f)
|
||||
_shouldResetButtons = true;
|
||||
|
||||
if (Input.mouseScrollDelta.y > 0 && Camera.main.orthographicSize > 3.0f)
|
||||
{
|
||||
_wasZooming = true;
|
||||
SetZoomSize(zoomIn: true);
|
||||
}
|
||||
else if (Input.mouseScrollDelta.y < 0 &&
|
||||
(GameState.IsDead || GameState.IsFreePlay || GameState.IsLobby) &&
|
||||
Camera.main.orthographicSize < 18.0f)
|
||||
{
|
||||
_wasZooming = true;
|
||||
SetZoomSize(zoomOut: true);
|
||||
}
|
||||
}
|
||||
@@ -69,13 +68,12 @@ internal class ZoomPatch
|
||||
(reset || Camera.main.orthographicSize == 3.0f) &&
|
||||
PlayerControl.LocalPlayer.IsAlive());
|
||||
|
||||
if (_shouldResetButtons)
|
||||
if (Camera.main.orthographicSize != _lastOrthographicSize)
|
||||
{
|
||||
ResolutionManager.ResolutionChanged.Invoke(
|
||||
(float)Screen.width / Screen.height,
|
||||
Screen.width, Screen.height,
|
||||
Screen.fullScreen);
|
||||
_shouldResetButtons = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
Reference in new issue
Block a user