diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings
index 6363112ab..fce429932 100644
--- a/EHR.sln.DotSettings
+++ b/EHR.sln.DotSettings
@@ -54,6 +54,9 @@
UI
VN
<Policy><Descriptor Staticness="Any" AccessRightKinds="Private, Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="non_field_members_should_be_pascal_case"><ElementKinds><Kind Name="PROPERTY" /><Kind Name="EVENT" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="False" Prefix="Get" Suffix="" Style="AaBb" /></Policy>
+
+ True
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="__" Suffix="" Style="aaBb" /></Policy></Policy>
True
True
True
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 4910defd2..44d58ddd0 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -812,7 +812,7 @@ internal static class CustomRolesHelper
public static bool OnlySpawnsWithPets(this CustomRoles role)
{
- return !(Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()) && role.SimpleAbilityTrigger() && role != CustomRoles.Chemist && !role.AlwaysUsesUnshift()) && OnlySpawnsWithPetsRoleList.Contains(role);
+ return !(((Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool())) || (Options.UsePhantomBasis.GetBool() && (!role.IsNeutral() || Options.UsePhantomBasisForNKs.GetBool()))) && role.SimpleAbilityTrigger() && role != CustomRoles.Chemist && !role.AlwaysUsesUnshift()) && OnlySpawnsWithPetsRoleList.Contains(role);
}
public static bool NeedUpdateOnLights(this CustomRoles role)
diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs
index af51367e7..c55e03a47 100644
--- a/Modules/DevManager.cs
+++ b/Modules/DevManager.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
+using UnityEngine;
using UnityEngine.Networking;
namespace EHR;
@@ -19,12 +20,27 @@ public static class DevManager
return Tags.GetValueOrDefault(code, DefaultDevUser);
}
+ private static int Attempts;
+
private static IEnumerator FetchTags()
{
+ Attempts++;
+
+ if (Attempts > 10)
+ {
+ Logger.Error($"Failed to fetch tags after {Attempts - 1} attempts.", "DevManager.FetchTags");
+ yield break;
+ }
+
UnityWebRequest request = UnityWebRequest.Get("https://gurge44.pythonanywhere.com/get_all_tags");
yield return request.SendWebRequest();
- if (request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError) { Logger.Error($"Error fetching tags: {request.error}", "DevManager.FetchTags"); }
+ if (request.result != UnityWebRequest.Result.Success)
+ {
+ Logger.Error($"Error fetching tags: {request.error}", "DevManager.FetchTags");
+ yield return new WaitForSeconds(300f);
+ yield return FetchTags();
+ }
else
{
try
diff --git a/Modules/RPC.cs b/Modules/RPC.cs
index ba17b8cfd..6d4a87812 100644
--- a/Modules/RPC.cs
+++ b/Modules/RPC.cs
@@ -841,7 +841,7 @@ internal static class RPCHandlerPatch
}
case CustomRPC.SyncLobbyTimer:
{
- GameStartManagerPatch.Timer = reader.ReadPackedInt32();
+ GameStartManagerPatch.TimerStartTS = long.Parse(reader.ReadString());
break;
}
case CustomRPC.SetGamerHealth:
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index 27b15c2a7..fc2db6722 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -504,6 +504,12 @@ internal static class ChatCommands
if (!player.Is(CustomRoles.NoteKiller) || args.Length < 2) return;
if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll();
+
+ if (!NoteKiller.CanGuess)
+ {
+ Utils.SendMessage(GetString("DeathNoteCommand.CanNotGuess"), player.PlayerId);
+ return;
+ }
var guess = args[1].ToLower();
guess = char.ToUpper(guess[0]) + guess[1..];
@@ -511,6 +517,7 @@ internal static class ChatCommands
if (deadPlayer == 0 && (!NoteKiller.RealNames.TryGetValue(0, out var name) || name != guess))
{
+ NoteKiller.CanGuess = false;
Utils.SendMessage(GetString("DeathNoteCommand.WrongName"), player.PlayerId);
return;
}
@@ -519,6 +526,7 @@ internal static class ChatCommands
if (pc == null || !pc.IsAlive())
{
+ NoteKiller.CanGuess = false;
Utils.SendMessage(GetString("DeathNoteCommand.PlayerNotFoundOrDead"), player.PlayerId);
return;
}
@@ -1696,9 +1704,13 @@ internal static class ChatCommands
else
Main.SetRoles[targetPc.PlayerId] = roleToSet;
- var playername = $"{Utils.ColorString(Main.PlayerColors.TryGetValue(resultId, out Color32 textColor) ? textColor : Color.white, targetPc.GetRealName())}";
- var rolename = $" {GetString(roleToSet.ToString())} ";
- Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("RoleSelected"), playername, rolename));
+ Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("RoleSelected"), resultId.ColoredPlayerName(), roleToSet.ToColoredString()));
+
+ if (roleToSet.OnlySpawnsWithPets() && !Options.UsePets.GetBool())
+ {
+ Options.UsePets.SetValue(1);
+ Utils.SendMessage(GetString("SetRoleUsePetsMessage"), player.PlayerId);
+ }
}
private static void UpCommand(ChatController __instance, PlayerControl player, string text, string[] args)
diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs
index 584e3f121..f120b35f0 100644
--- a/Patches/GameStartManagerPatch.cs
+++ b/Patches/GameStartManagerPatch.cs
@@ -27,7 +27,8 @@ public static class GameStartManagerUpdatePatch
public static class GameStartManagerPatch
{
- public static float Timer { get; set; } = 600f;
+ public static float Timer => Math.Max(0, 600f - (Utils.TimeStamp - TimerStartTS));
+ public static long TimerStartTS;
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))]
public static class GameStartManagerStartPatch
@@ -67,7 +68,7 @@ public static class GameStartManagerPatch
__instance.GameRoomNameCode.text = GameCode.IntToGameName(AmongUsClient.Instance.GameId);
// Reset lobby countdown timer
- Timer = 600f;
+ TimerStartTS = Utils.TimeStamp;
HideName = Object.Instantiate(__instance.GameRoomNameCode, __instance.GameRoomNameCode.transform);
@@ -145,7 +146,9 @@ public static class GameStartManagerPatch
{
Main.UpdateTime = 0;
- if (((GameData.Instance?.PlayerCount >= MinPlayer && Timer <= MinWait) || Timer <= MaxWait) && !GameStates.IsCountDown)
+ float timer = Timer;
+
+ if (((GameData.Instance?.PlayerCount >= MinPlayer && timer <= MinWait) || timer <= MaxWait) && !GameStates.IsCountDown)
{
PlayerControl[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).ToArray();
@@ -331,11 +334,12 @@ public static class GameStartManagerPatch
// Lobby timer
if (!GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return;
- Timer = Mathf.Max(0f, Timer -= Time.deltaTime);
- int minutes = (int)Timer / 60;
- int seconds = (int)Timer % 60;
+ float timer = Timer;
+
+ int minutes = (int)timer / 60;
+ int seconds = (int)timer % 60;
var suffix = $"{minutes:00}:{seconds:00}";
- if (Timer <= 60) suffix = Utils.ColorString((int)Timer % 2 == 0 ? Color.yellow : Color.red, suffix);
+ if (timer <= 60) suffix = Utils.ColorString((int)timer % 2 == 0 ? Color.yellow : Color.red, suffix);
if (Options.NoGameEnd.GetBool())
suffix = suffix.Insert(0, Utils.ColorString(Color.yellow, $"{GetString("NoGameEnd").ToUpper()}") + Utils.ColorString(Color.gray, " | "));
diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs
index 22a92aefb..d60cb8725 100644
--- a/Patches/PlayerJoinAndLeftPatch.cs
+++ b/Patches/PlayerJoinAndLeftPatch.cs
@@ -385,7 +385,7 @@ internal static class InnerNetClientSpawnPatch
else
{
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncLobbyTimer, SendOption.Reliable, client.Id);
- writer.WritePacked((int)GameStartManagerPatch.Timer);
+ writer.WritePacked((int)GameStartManagerPatch.TimerStartTS);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
}
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 52e9e2789..41ec3775f 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -2771,7 +2771,7 @@
"JudgeCanTrialInfected": "Can trial Infected",
"JudgeCanTrialContagious": "Can trial Contagious",
"PostLobbyCodeToEHRDiscordServer": "Post the lobby code to the EHR Discord server",
- "Message.LobbyCodeSent": "<#00ffa5>✓ <#00a5ff>The lobby code has been sent to the EHR Discord server.\\nYou can disable this feature in the settings (System Settings tab).",
+ "Message.LobbyCodeSent": "<#00ffa5>✓ <#00a5ff>The lobby code has been sent to the EHR website. This will help others find EHR lobbies.\\nYou can disable this feature in the settings (System Settings tab).",
"ActiveRolesList": "Active Roles List",
"ForExample": "Example Use",
"HideGameCodes": "Hide Game Code",
@@ -3770,7 +3770,7 @@
"TeleportedByTornado": "\\nOh no! You just got caught up in a tornado,\\nand you flew to over here!",
"Deadlined": "Deadlined",
"DeadlinedInfo": "Must do a task or use kill button each round",
- "DeadlinedInfoLong": "(Add-ons):\\nAs the Deadlined, you must complete a task or kill someone each round. If you fail to do so, you will die. If a meeting gets called too soon after the previous one ends (time set by the host), you won't die. This doesn't apply on the first round.",
+ "DeadlinedInfoLong": "(Add-ons):\\nAs the Deadlined, you must complete a task or use your kill button (not necessarily for directly killing) on someone each round. If you fail to do so, you will die. If a meeting gets called too soon after the previous one ends (time set by the host), you won't die. This doesn't apply on the first round.",
"Prefix.Deadlined": "Deadlined",
"Deadlined.InactiveTime": "Safe Time After Meetings",
"Deadlined.MustDoTask": "<#ffa500>You must complete a task or kill before the next meeting",
@@ -4269,6 +4269,7 @@
"NKWC.XKills": "Kill X Players",
"NoteKiller.NumPlayersToKill": "Number of Players to Kill (X =)",
"NoteKiller.OthersSelfSuffix": "There's a {0} in the game. Your real name is <#00ffa5>{1}",
+ "DeathNoteCommand.CanNotGuess": "You've already tried to guess someone's real name and failed, so you can't guess again until the next meeting.",
"DeathNoteCommand.WrongName": "You've entered the wrong name.",
"DeathNoteCommand.PlayerNotFoundOrDead": "The player with this name was not found or is already dead.",
"DeathNoteCommand.Success": "Success! {0} was killed.",
diff --git a/Roles/Neutral/NoteKiller.cs b/Roles/Neutral/NoteKiller.cs
index c07789352..5d178d15c 100644
--- a/Roles/Neutral/NoteKiller.cs
+++ b/Roles/Neutral/NoteKiller.cs
@@ -26,14 +26,22 @@ public class NoteKiller : RoleBase
private static readonly string[] Names =
[
- "John", "Jane", "Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank",
- "Ivy", "Jack", "Kate", "Liam", "Mia", "Nina", "Oliver", "Penny", "Quinn", "Ryan"
+ "John", "Jane", "Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Hank",
+ "Ivy", "Jack", "Kate", "Liam", "Nina", "Oliver", "Penny", "Quinn", "Ryan",
+ "Thomas", "Uma", "Victor", "William", "Yang", "Zach", "Zoe", "Xavier", "Zoey",
+ "Abigail", "Alex", "Amelia", "Ava", "Bella", "Charlotte", "Chloe", "Daisy", "Emily",
+ "Ella", "Evelyn", "Faith", "Florence", "Grace", "Hannah", "Isabella", "Isabelle",
+ "Isabel", "Jasmine", "Jocelyn", "Julia", "Katherine", "Lily", "Madeline", "Madison",
+ "Madelyn", "Margaret", "Maria", "Matilda", "Mia", "Mila", "Miranda", "Natalie",
+ "Nora", "Olivia", "Penelope", "Piper", "Poppy", "Riley", "Rose", "Sophia", "Sofia",
+ "Sophie", "Stella", "Sydney", "Taylor", "Victoria", "Violet", "Willow", "Xander"
];
public static Dictionary RealNames = [];
private static Dictionary ShownClues = [];
private static long ShowClueEndTimeStamp;
public static int Kills;
+ public static bool CanGuess;
private static byte NoteKillerID;
@@ -82,6 +90,7 @@ public class NoteKiller : RoleBase
{
On = true;
NoteKillerID = playerId;
+ CanGuess = true;
}
public override bool CanUseKillButton(PlayerControl pc)
@@ -170,6 +179,11 @@ public class NoteKiller : RoleBase
return false;
}
+ public override void AfterMeetingTasks()
+ {
+ CanGuess = true;
+ }
+
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())