Merge remote-tracking branch 'origin/main'

This commit is contained in:
Gurge44 committed 2026-02-16 08:31:37 +01:00
commit 3bcab47d4e
6 files changed
+195 -144

No files matched your search

+86 -35
View File
@@ -1,10 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
using EHR.Roles;
using HarmonyLib;
using Hazel;
using UnityEngine;
namespace EHR.Gamemodes;
@@ -25,7 +26,6 @@ internal static class CustomHnS
public static Dictionary<Team, Dictionary<CustomRoles, int>> HideAndSeekRoles = [];
public static Dictionary<byte, (IHideAndSeekRole Interface, CustomRoles Role)> PlayerRoles = [];
public static Dictionary<byte, int> Danger = [];
public static List<CustomRoles> AllHnSRoles = [];
public static int SeekerNum => Math.Max(Main.RealOptionsData.GetInt(Int32OptionNames.NumImpostors), 1);
@@ -248,6 +248,8 @@ internal static class CustomHnS
byte agent = result.GetKeyByValue(CustomRoles.Agent).PlayerId;
PlayerRoles.DoIf(x => x.Value.Role != CustomRoles.Agent && x.Value.Interface.Team == Team.Impostor, x => TargetArrow.Add(x.Key, agent));
}
SendRPC();
}
public static void ApplyGameOptions(IGameOptions opt, PlayerControl pc)
@@ -290,7 +292,7 @@ internal static class CustomHnS
if (!PlayerRoles.TryGetValue(playerInfo.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) role)) return false;
return role.Interface.Team == Team.Crewmate || role.Role == CustomRoles.Taskinator;
return (role.Interface.Team == Team.Crewmate || role.Role == CustomRoles.Taskinator) && role.Role != CustomRoles.GM;
}
public static bool IsRoleTextEnabled(PlayerControl seer, PlayerControl target)
@@ -303,9 +305,39 @@ internal static class CustomHnS
return targetRole.Interface.Team == Team.Impostor && (targetRole.Role != CustomRoles.Agent || seerRole.Interface.Team == Team.Impostor);
}
public static string GetTaskBarText()
{
string text = Main.PlayerStates.IntersectBy(PlayerRoles.Keys, x => x.Key).Aggregate("<size=80%>", (current, state) => $"{current}{GetStateText(state)}\n");
return $"{text}</size>\r\n\r\n<#00ffa5>{Translator.GetString("HNS.TaskCount")}</color> {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}";
static string GetStateText(KeyValuePair<byte, PlayerState> state)
{
string name = Main.AllPlayerNames.GetValueOrDefault(state.Key, $"ID {state.Key}");
name = Utils.ColorString(Main.PlayerColors.GetValueOrDefault(state.Key, Color.white), name);
bool isSeeker = PlayerRoles[state.Key].Interface.Team == Team.Impostor;
bool alive = !state.Value.IsDead;
TaskState taskState = state.Value.TaskState;
var stateText = string.Empty;
if (PlayersSeeRoles.GetBool())
stateText = $" ({GetRole().ToColoredString()}){GetTaskCount()}";
else if (isSeeker) stateText = $" ({CustomRoles.Seeker.ToColoredString()})";
if (!alive) stateText += $" <color=#ff0000>{Translator.GetString("Dead")}</color>";
stateText = $"{name}{stateText}";
return stateText;
CustomRoles GetRole() => state.Value.MainRole == CustomRoles.Agent ? CustomRoles.Hider : state.Value.MainRole;
string GetTaskCount() => state.Value.MainRole == CustomRoles.GM || CustomRoles.Agent.IsEnable() || !taskState.HasTasks ? string.Empty : $" ({taskState.CompletedTasksCount}/{taskState.AllTasksCount})";
}
}
public static string GetSuffixText(PlayerControl seer, PlayerControl target, bool hud = false)
{
if (GameStates.IsLobby || Options.CurrentGameMode != CustomGameMode.HideAndSeek || Main.HasJustStarted || seer.PlayerId != target.PlayerId || (seer.IsHost() && !hud) || TimeLeft < 0) return string.Empty;
if (Main.HasJustStarted || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || TimeLeft < 0) return string.Empty;
string dangerMeter = GetDangerMeter(seer);
@@ -354,6 +386,53 @@ internal static class CustomHnS
return $"<size=90%>{Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), seer.GetRoleInfo())}</size>";
}
public static void SendRPC()
{
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.HNSSync, SendOption.Reliable);
writer.Write(TimeLeft);
writer.Write(Danger.Count);
foreach (var kv in Danger)
{
writer.Write(kv.Key);
writer.Write(kv.Value);
}
writer.Write(PlayerRoles.Count);
foreach (var kv in PlayerRoles)
{
writer.Write(kv.Key);
writer.Write((int)kv.Value.Role);
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
public static void ReceiveRPC(MessageReader reader)
{
TimeLeft = reader.ReadInt32();
int dangerCount = reader.ReadInt32();
Danger.Clear();
for (int i = 0; i < dangerCount; i++)
{
byte id = reader.ReadByte();
int danger = reader.ReadInt32();
Danger[id] = danger;
}
int roleCount = reader.ReadInt32();
PlayerRoles.Clear();
for (int i = 0; i < roleCount; i++)
{
byte id = reader.ReadByte();
CustomRoles role = (CustomRoles)reader.ReadInt32();
var roleInterface = role == CustomRoles.GM ? new Hider() : (IHideAndSeekRole)Activator.CreateInstance(Main.AllTypes.First(t => t.Name == role.ToString()));
PlayerRoles[id] = (roleInterface, role);
}
Main.HasJustStarted = false;
Utils.MarkEveryoneDirtySettingsV4();
}
public static bool CheckForGameEnd(out GameOverReason reason)
{
reason = GameOverReason.ImpostorsByKill;
@@ -404,36 +483,6 @@ internal static class CustomHnS
CustomWinnerHolder.WinnerIds.UnionWith(foxes);
}
public static string GetTaskBarText()
{
string text = Main.PlayerStates.IntersectBy(PlayerRoles.Keys, x => x.Key).Aggregate("<size=80%>", (current, state) => $"{current}{GetStateText(state)}\n");
return $"{text}</size>\r\n\r\n<#00ffa5>{Translator.GetString("HNS.TaskCount")}</color> {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}";
static string GetStateText(KeyValuePair<byte, PlayerState> state)
{
string name = Main.AllPlayerNames.GetValueOrDefault(state.Key, $"ID {state.Key}");
name = Utils.ColorString(Main.PlayerColors.GetValueOrDefault(state.Key, Color.white), name);
bool isSeeker = PlayerRoles[state.Key].Interface.Team == Team.Impostor;
bool alive = !state.Value.IsDead;
TaskState ts = state.Value.TaskState;
var stateText = string.Empty;
if (PlayersSeeRoles.GetBool())
stateText = $" ({GetRole().ToColoredString()}){GetTaskCount()}";
else if (isSeeker) stateText = $" ({CustomRoles.Seeker.ToColoredString()})";
if (!alive) stateText += $" <color=#ff0000>{Translator.GetString("Dead")}</color>";
stateText = $"{name}{stateText}";
return stateText;
CustomRoles GetRole() => state.Value.MainRole == CustomRoles.Agent ? CustomRoles.Hider : state.Value.MainRole;
string GetTaskCount() => CustomRoles.Agent.IsEnable() || !ts.HasTasks ? string.Empty : $" ({ts.CompletedTasksCount}/{ts.AllTasksCount})";
}
}
public static void OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (killer == null || target == null || PlayerRoles[killer.PlayerId].Interface.Team != Team.Impostor || PlayerRoles[target.PlayerId].Interface.Team == Team.Impostor || IsBlindTime) return;
@@ -554,6 +603,8 @@ internal static class CustomHnS
if (DangerMeter.GetBool() || (TimeLeft + 1) % 60 == 0 || TimeLeft <= 60)
Utils.NotifyRoles();
SendRPC();
}
}
}
}
+69 -80
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
@@ -67,9 +67,9 @@ public static class CustomSoundsManager
return;
}
FileStream fs = File.Create(path);
stream.CopyTo(fs);
fs.Close();
FileStream fileStream = File.Create(path);
stream.CopyTo(fileStream);
fileStream.Close();
}
StartPlay(path, volume, pitch);
@@ -82,101 +82,94 @@ public static class CustomSoundsManager
{
if (!audioCache.TryGetValue(path, out var clip))
{
clip = LoadWav(path);
clip = LoadWAV(path);
audioCache[path] = clip;
}
if (clip != null)
{
SoundManager.Instance.PlaySoundImmediate(clip, false, volume);
}
}
public static AudioClip LoadWav(string path)
private static AudioClip LoadWAV(string path)
{
byte[] fileData = File.ReadAllBytes(path);
WAV wav = new(fileData);
AudioClip clip = AudioClip.Create(Path.GetFileNameWithoutExtension(path), wav.SampleCount, 1, wav.Frequency, false, false);
Logger.Info($"[WAV: LeftChannel={wav.LeftChannel}, RightChannel={wav.RightChannel}, ChannelCount={wav.ChannelCount}, SampleCount={wav.SampleCount}, Frequency={wav.Frequency}]", "CustomSounds");
AudioClip clip = AudioClip.Create(Path.GetFileNameWithoutExtension(path), wav.SampleCount, 1, wav.Frequency, false, false);
clip.SetData(wav.LeftChannel, 0);
return clip;
}
public class WAV {
private class WAV
{
// Convert two bytes to one float in the range -1 to 1
private static float BytesToFloat(byte firstByte, byte secondByte)
{
// Convert two bytes to one short (little endian)
short s = (short)((secondByte << 8) | firstByte);
// Convert to range from -1 to (just below) 1
return s / 32768.0F;
}
// convert two bytes to one float in the range -1 to 1
static float BytesToFloat(byte firstByte, byte secondByte) {
// convert two bytes to one short (little endian)
short s = (short)((secondByte << 8) | firstByte);
// convert to range from -1 to (just below) 1
return s / 32768.0F;
}
private static int BytesToInt(byte[] bytes, int offset = 0)
{
int value = 0;
static int BytesToInt(byte[] bytes, int offset = 0){
int value=0;
for (int i=0;i<4;i++)
for (int i = 0; i < 4; i++)
value |= bytes[offset + i] << (i * 8);
return value;
}
// Properties
public float[] LeftChannel { get; }
public float[] RightChannel { get; }
public int ChannelCount { get; }
public int SampleCount { get; }
public int Frequency { get; }
public WAV(byte[] wav)
{
// Determine if mono or stereo
ChannelCount = wav[22]; // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels
// Get the frequency
Frequency = BytesToInt(wav, 24);
// Get past all the other sub chunks to get to the data subchunk:
int pos = 12; // First Subchunk ID from 12 to 16
// Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
while (!(wav[pos] == 100 && wav[pos + 1] == 97 && wav[pos + 2] == 116 && wav[pos + 3] == 97))
{
value |= bytes[offset + i] << (i*8);
}
return value;
}
pos += 4;
int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
pos += 4 + chunkSize;
}
private static byte[] GetBytes(string filename){
return File.ReadAllBytes(filename);
}
// properties
public float[] LeftChannel{get; internal set;}
public float[] RightChannel{get; internal set;}
public int ChannelCount {get; internal set;}
public int SampleCount {get; internal set;}
public int Frequency {get; internal set;}
// Returns left and right double arrays. 'right' will be null if sound is mono.
public WAV(string filename):
this(GetBytes(filename)) {}
public WAV(byte[] wav){
// Determine if mono or stereo
ChannelCount = wav[22]; // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels
// Get the frequency
Frequency = BytesToInt(wav, 24);
// Get past all the other sub chunks to get to the data subchunk:
int pos = 12; // First Subchunk ID from 12 to 16
// Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
while (!(wav[pos] == 100 && wav[pos+1] == 97 && wav[pos+2] == 116 && wav[pos+3] == 97))
{
pos += 4;
int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
pos += 4 + chunkSize;
}
pos += 4; // skip "data"
pos += 4; // skip "data"
int dataSize = BytesToInt(wav, pos);
pos += 4; // now at PCM data
// Pos is now positioned to start of actual sound data.
SampleCount = dataSize / 2; // 2 bytes per sample (16 bit sound mono)
if (ChannelCount == 2) SampleCount /= 2; // 4 bytes per sample (16 bit stereo)
// Allocate memory (right will be null if only mono sound)
LeftChannel = new float[SampleCount];
if (ChannelCount == 2) RightChannel = new float[SampleCount];
else RightChannel = null;
pos += 4; // now at PCM data
// Pos is now positioned to start of actual sound data.
SampleCount = dataSize / 2; // 2 bytes per sample (16 bit sound mono)
if (ChannelCount == 2) SampleCount /= 2; // 4 bytes per sample (16 bit stereo)
// Allocate memory (right will be null if only mono sound)
LeftChannel = new float[SampleCount];
if (ChannelCount == 2) RightChannel = new float[SampleCount];
else RightChannel = null;
int end = pos + dataSize;
// Write to double array/s:
int i = 0;
while (pos + (ChannelCount * 2) <= end && i < SampleCount) {
LeftChannel[i] = BytesToFloat(wav[pos], wav[pos + 1]);
// Write to double array/s:
int i = 0;
pos += 2;
while (pos + (ChannelCount * 2) <= end && i < SampleCount)
{
LeftChannel[i] = BytesToFloat(wav[pos], wav[pos + 1]);
pos += 2;
if (ChannelCount == 2)
if (ChannelCount == 2)
{
RightChannel[i] = BytesToFloat(wav[pos], wav[pos + 1]);
pos += 2;
@@ -185,6 +178,7 @@ public static class CustomSoundsManager
}
}
// Returns left and right double arrays. 'right' will be null if sound is mono.
public float[] GetStereoData()
{
if (RightChannel == null) return LeftChannel;
@@ -199,10 +193,5 @@ public static class CustomSoundsManager
return stereoData;
}
public override string ToString()
{
return $"[WAV: LeftChannel={LeftChannel}, RightChannel={RightChannel}, ChannelCount={ChannelCount}, SampleCount={SampleCount}, Frequency={Frequency}]";
}
}
}
}
+6 -1
View File
@@ -166,6 +166,7 @@ public enum CustomRPC
FFAKill,
FFASync,
QuizSync,
HNSSync,
HotPotatoSync,
SoloPVPSync,
CTFSync,
@@ -1217,6 +1218,11 @@ internal static class RPCHandlerPatch
Quiz.AllowKills = reader.ReadBoolean();
break;
}
case CustomRPC.HNSSync:
{
CustomHnS.ReceiveRPC(reader);
break;
}
case CustomRPC.HotPotatoSync:
{
HotPotato.ReceiveRPC(reader);
@@ -1690,5 +1696,4 @@ internal static class PlayerPhysicsRPCHandlerPatch
return true;
}
}
+5 -4
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -401,13 +401,14 @@ public class AbstractPagingBehaviour(IntPtr ptr) : MonoBehaviour(ptr)
public virtual void Update()
{
bool chatIsOpen = HudManager.Instance.Chat.IsOpenOrOpening;
bool gameMenuIsOpen = HudManager.Instance.GameMenu.IsOpen;
if (Input.touchSupported)
{
foreach (Touch touch in Input.touches)
{
if (touch.phase != TouchPhase.Moved) continue;
if (chatIsOpen) break;
if (chatIsOpen || gameMenuIsOpen) break;
if (touch.deltaPosition.y > 0f)
{
@@ -422,9 +423,9 @@ public class AbstractPagingBehaviour(IntPtr ptr) : MonoBehaviour(ptr)
}
}
if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.LeftArrow) || (!chatIsOpen && Input.mouseScrollDelta.y > 0f))
if (!chatIsOpen && !gameMenuIsOpen && (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.LeftArrow) || Input.mouseScrollDelta.y > 0f))
Cycle(false);
else if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.RightArrow) || (!chatIsOpen && Input.mouseScrollDelta.y < 0f))
else if (!chatIsOpen && !gameMenuIsOpen && (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.RightArrow) || Input.mouseScrollDelta.y < 0f))
Cycle(true);
}
+3 -4
View File
@@ -275,7 +275,7 @@ internal static class HudManagerPatch
CustomGameMode.FFA => FreeForAll.GetHudText(),
CustomGameMode.StopAndGo => StopAndGo.GetHudText(),
CustomGameMode.HotPotato => HotPotato.GetSuffixText(player.PlayerId, true),
CustomGameMode.HideAndSeek when player.IsHost() => CustomHnS.GetSuffixText(player, player, true),
CustomGameMode.HideAndSeek => CustomHnS.GetSuffixText(player, player, true),
CustomGameMode.NaturalDisasters => NaturalDisasters.SuffixText(),
CustomGameMode.Deathrace => Deathrace.GetSuffix(player, player, true),
CustomGameMode.Snowdown => Snowdown.GetHudText(),
@@ -1172,7 +1172,7 @@ internal static class TaskPanelBehaviourPatch
finalTextBuilder.Append("\r\n\r\n").Append(string.Join('\n', summaryText4));
break;
}
case CustomGameMode.HideAndSeek when AmongUsClient.Instance.AmHost:
case CustomGameMode.HideAndSeek:
{
finalTextBuilder.Append("\r\n\r\n").Append(CustomHnS.GetTaskBarText());
break;
@@ -1426,5 +1426,4 @@ internal static class RepairSender
return SystemType + "(" + (SystemTypes)SystemType + ")\r\n" + Amount;
}
}
#endif
#endif
+26 -20
View File
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
@@ -20,29 +21,34 @@ public static class ServerDropdownPatch
public static bool Prefix(ServerDropdown __instance)
{
if (SceneManager.GetActiveScene().name == "FindAGame") return true;
SpriteRenderer bg = __instance.background;
bg.size = new Vector2(4, 1);
ServerManager sm = ServerManager.Instance;
TranslationController tc = TranslationController.Instance;
int totalCols = Mathf.Max(1, Mathf.CeilToInt(sm.AvailableRegions.Length / (float)5));
int rowLimit = Mathf.Min(sm.AvailableRegions.Length, 5);
SpriteRenderer background = __instance.background;
background.size = new Vector2(4, 1);
ServerManager serverManager = ServerManager.Instance;
TranslationController translationController = TranslationController.Instance;
var regions = serverManager.AvailableRegions.ToList();
IRegionInfo currentRegion = serverManager.CurrentRegion;
var displayRegions = regions.Where(region => region.Name != currentRegion.Name).ToList();
int totalColumns = Mathf.Max(1, Mathf.CeilToInt(displayRegions.Count / 5f));
int rowLimit = Mathf.Min(displayRegions.Count, 5);
__instance.defaultButtonSelected = __instance.firstOption;
__instance.firstOption.ChangeButtonText(translationController.GetStringWithDefault(currentRegion.TranslateName, currentRegion.Name, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
for (var index = 0; index < sm.AvailableRegions.Length; index++)
for (var index = 0; index < displayRegions.Count; index++)
{
IRegionInfo ri = sm.AvailableRegions[index];
var b = __instance.ButtonPool.Get<ServerListButton>();
b.transform.localPosition = new Vector3(((index / 5) - ((totalCols - 1) / 2f)) * 3.15f, __instance.y_posButton - (0.5f * (index % 5)), -1f);
b.Text.text = tc.GetStringWithDefault(ri.TranslateName, ri.Name, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
b.Text.ForceMeshUpdate();
b.Button.OnClick.RemoveAllListeners();
b.Button.OnClick.AddListener((Action)(() => __instance.ChooseOption(ri)));
__instance.controllerSelectable.Add(b.Button);
IRegionInfo regionInfo = displayRegions[index];
var buttonPool = __instance.ButtonPool.Get<ServerListButton>();
buttonPool.transform.localPosition = new Vector3(((index / 5) - ((totalColumns - 1) / 2f)) * 3.15f, __instance.y_posButton - (0.5f * (index % 5)), -1f);
buttonPool.Text.text = translationController.GetStringWithDefault(regionInfo.TranslateName, regionInfo.Name, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
buttonPool.Text.ForceMeshUpdate();
buttonPool.Button.OnClick.RemoveAllListeners();
buttonPool.Button.OnClick.AddListener((Action)(() => __instance.ChooseOption(regionInfo)));
__instance.controllerSelectable.Add(buttonPool.Button);
}
float h = 1.2f + (0.5f * (rowLimit - 1));
float w = totalCols > 1 ? (3.15f * (totalCols - 1)) + bg.size.x : bg.size.x;
bg.transform.localPosition = new Vector3(0f, __instance.initialYPos - ((h - 1.2f) / 2f), 0f);
bg.size = new Vector2(w, h);
float height = 1.2f + (0.5f * (rowLimit - 1));
float width = totalColumns > 1 ? (3.15f * (totalColumns - 1)) + background.size.x : background.size.x;
background.transform.localPosition = new Vector3(0f, __instance.initialYPos - ((height - 1.2f) / 2f), 0f);
background.size = new Vector2(width, height);
return false;
}
}
}