diff --git a/EHR.csproj b/EHR.csproj
index 4b919b20a..a31768c5f 100644
--- a/EHR.csproj
+++ b/EHR.csproj
@@ -7,7 +7,7 @@
EHR
Gurge44
preview
- C:\Program Files\Epic Games\AmongUs
+ E:\Program Files\AmongUs
true
True
$(MSBuildProjectName)
diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings
index 326108f5a..a7565ff16 100644
--- a/EHR.sln.DotSettings
+++ b/EHR.sln.DotSettings
@@ -8,6 +8,7 @@
CD
CK
CNO
+ CRLF
CTF
DLL
DY
diff --git a/GameMode/HnSManager.cs b/GameMode/HnSManager.cs
index 8bb6316cd..aaee1c055 100644
--- a/GameMode/HnSManager.cs
+++ b/GameMode/HnSManager.cs
@@ -113,7 +113,7 @@ namespace EHR
public static void AssignRoles()
{
Dictionary result = [];
- List allPlayers = [.. Main.AllAlivePlayerControls];
+ List allPlayers = [.. Main.AllPlayerControls];
if (Main.GM.Value)
{
@@ -270,7 +270,7 @@ namespace EHR
public static bool HasTasks(NetworkedPlayerInfo playerInfo)
{
- var role = PlayerRoles[playerInfo.PlayerId];
+ if (!PlayerRoles.TryGetValue(playerInfo.PlayerId, out var role)) return false;
return role.Interface.Team == Team.Crewmate || role.Role == CustomRoles.Taskinator;
}
@@ -448,11 +448,17 @@ namespace EHR
PlayerRoles = PlayerRoles.IntersectBy(Main.AllPlayerControls.Select(x => x.PlayerId), x => x.Key).ToDictionary(x => x.Key, x => x.Value);
- var idPosPairs = PlayerRoles.Join(Main.AllPlayerControls, x => x.Key, x => x.PlayerId, (role, pc) => (role.Key, pc)).ToDictionary(x => x.Key, x => x.pc.Pos());
- var imps = PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).ToDictionary(x => x.Key, x => idPosPairs[x.Key]);
- var nonImps = PlayerRoles.Where(x => x.Value.Interface.Team is Team.Crewmate or Team.Neutral).ToArray();
- ClosestImpostor = nonImps.ToDictionary(x => x.Key, x => imps.MinBy(y => Vector2.Distance(y.Value, idPosPairs[x.Key])).Key);
- Danger = nonImps.ToDictionary(x => x.Key, x => Math.Clamp(((1 + (int)Math.Ceiling(Vector2.Distance(idPosPairs[x.Key], idPosPairs[ClosestImpostor[x.Key]]))) / 3) - 1, 0, 5));
+ try
+ {
+ var idPosPairs = PlayerRoles.Join(Main.AllPlayerControls, x => x.Key, x => x.PlayerId, (role, pc) => (role.Key, pc)).ToDictionary(x => x.Key, x => x.pc.Pos());
+ var imps = PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).ToDictionary(x => x.Key, x => idPosPairs[x.Key]);
+ var nonImps = PlayerRoles.Where(x => x.Value.Interface.Team is Team.Crewmate or Team.Neutral).ToArray();
+ ClosestImpostor = nonImps.ToDictionary(x => x.Key, x => imps.MinBy(y => Vector2.Distance(y.Value, idPosPairs[x.Key])).Key);
+ Danger = nonImps.ToDictionary(x => x.Key, x => Math.Clamp(((1 + (int)Math.Ceiling(Vector2.Distance(idPosPairs[x.Key], idPosPairs[ClosestImpostor[x.Key]]))) / 3) - 1, 0, 5));
+ }
+ catch
+ {
+ }
if (DangerMeter.GetBool() || (TimeLeft + 1) % 60 == 0 || TimeLeft <= 60) Utils.NotifyRoles();
}
diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs
index 713886a73..4f29c1986 100644
--- a/Modules/AntiBlackout.cs
+++ b/Modules/AntiBlackout.cs
@@ -173,8 +173,6 @@ public static class AntiBlackout
target.RpcSetRoleDesync(changedRoleType, seer.GetClientId());
}
-
- ResetAllCooldowns();
}
private static void ResetAllCooldowns()
@@ -197,6 +195,7 @@ public static class AntiBlackout
{
SkipTasks = false;
ExilePlayerId = -1;
+ ResetAllCooldowns();
}
public static void Reset()
diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs
index 9b7ddda0a..3c337d3b0 100644
--- a/Modules/Camouflague.cs
+++ b/Modules/Camouflague.cs
@@ -56,10 +56,6 @@ public static class Camouflage
0 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", "", ""), // Default
1 => new NetworkedPlayerInfo.PlayerOutfit().Set("", DataManager.Player.Customization.Color, DataManager.Player.Customization.Hat, DataManager.Player.Customization.Skin, DataManager.Player.Customization.Visor, DataManager.Player.Customization.Pet, ""), // Host
2 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 13, "hat_pk05_Plant", "", "visor_BubbleBumVisor", "", ""), // Karpe
- 3 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 13, "hat_rabbitEars", "skin_Bananaskin", "visor_BubbleBumVisor", "pet_Pusheen", ""), // Lauryn
- 4 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 0, "hat_mira_headset_yellow", "skin_SuitB", "visor_lollipopCrew", "pet_EmptyPet", ""), // Moe
- 5 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 17, "hat_pkHW01_Witch", "skin_greedygrampaskin", "visor_Plsno", "pet_Pusheen", ""), // Pyro
- 6 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 7, "hat_crownDouble", "skin_D2Saint14", "visor_anime", "pet_Bush", ""), // ryuk
7 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 7, "hat_pk04_Snowman", "", "", "", ""), // Gurge44
8 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 17, "hat_baseball_Black", "skin_Scientist-Darkskin", "visor_pusheenSmileVisor", "pet_Pip", ""), // TommyXL
_ => CamouflageOutfit
@@ -89,6 +85,8 @@ public static class Camouflage
if (oldIsCamouflage != IsCamouflage)
{
+ Logger.Info($"IsCamouflage: {IsCamouflage}", "CheckCamouflage");
+
WaitingForSkinChange = [];
foreach (var pc in Main.AllPlayerControls)
@@ -115,6 +113,8 @@ public static class Camouflage
{
if (!AmongUsClient.Instance.AmHost || (!Options.CommsCamouflage.GetBool() && !Camouflager.On) || target == null || (BlockCamouflage && !ForceRevert && !RevertToDefault && !GameEnd)) return;
+ Logger.Info($"New outfit for {target.GetNameWithRole()}", "Camouflage.RpcSetSkin");
+
var id = target.PlayerId;
if (IsCamouflage && Main.PlayerStates[id].IsDead) return;
@@ -140,13 +140,17 @@ public static class Camouflage
newOutfit = PlayerSkins[id];
}
}
-
+
SetPetForOutfitIfNecessary(newOutfit);
- // if the current Outfit is the same, return it
- if (newOutfit.Compare(target.Data.DefaultOutfit)) return;
+ // if the current Outfit is the same, return
+ if (newOutfit.Compare(target.Data.DefaultOutfit))
+ {
+ Logger.Info("Outfit is the same, returning", "Camouflage.RpcSetSkin");
+ return;
+ }
- Logger.Info($"newOutfit={newOutfit.GetString()}", "RpcSetSkin");
+ Logger.Info($"Setting new outfit: {newOutfit.GetString()}", "Camouflage.RpcSetSkin");
var sender = CustomRpcSender.Create(name: $"Camouflage.RpcSetSkin({target.Data.PlayerName})");
diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs
index 4c0bf39cc..6c2253920 100644
--- a/Modules/CustomRolesHelper.cs
+++ b/Modules/CustomRolesHelper.cs
@@ -167,7 +167,7 @@ internal static class CustomRolesHelper
CustomRoles.SuperStar => CustomRoles.Crewmate,
CustomRoles.Hacker => CustomRoles.Shapeshifter,
CustomRoles.Visionary => CustomRoles.Shapeshifter,
- CustomRoles.Assassin => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
+ CustomRoles.Assassin => CustomRoles.Phantom,
CustomRoles.Undertaker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
CustomRoles.Luckey => CustomRoles.Crewmate,
CustomRoles.CyberStar => CustomRoles.Crewmate,
@@ -198,7 +198,7 @@ internal static class CustomRolesHelper
CustomRoles.GuessManagerRole => CustomRoles.Crewmate,
CustomRoles.Bane => CustomRoles.Crewmate,
CustomRoles.Transmitter => CustomRoles.Crewmate,
- CustomRoles.Altruist => CustomRoles.Engineer,
+ CustomRoles.Altruist => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer,
CustomRoles.Negotiator => CustomRoles.Crewmate,
CustomRoles.Grappler => CustomRoles.Crewmate,
CustomRoles.Journalist => CustomRoles.Crewmate,
@@ -824,7 +824,7 @@ internal static class CustomRolesHelper
{
CustomRoles.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker => false,
CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false,
- CustomRoles.AntiTP when pc.Is(CustomRoles.Transmitter) => false,
+ CustomRoles.AntiTP when pc.GetCustomRole() is CustomRoles.Transmitter or CustomRoles.Miner or CustomRoles.Escapee or CustomRoles.Tunneler => false,
CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false,
CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist => false,
CustomRoles.Egoist when pc.Is(CustomRoles.Gangster) => false,
diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs
index 650f11490..2fa3ed84d 100644
--- a/Modules/Debugger.cs
+++ b/Modules/Debugger.cs
@@ -62,7 +62,7 @@ static class Logger
private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, int lineNumber = 0, string fileName = "", bool multiLine = false)
{
- if (!IsEnable || DisableList.Contains(tag)) return;
+ if (!IsEnable || DisableList.Contains(tag) || (level == LogLevel.Debug && !DebugModeManager.AmDebugger)) return;
var logger = Main.Logger;
if (SendToGameList.Contains(tag) || IsAlsoInGame)
diff --git a/Modules/EAC.cs b/Modules/EAC.cs
index 83ed35895..84675b7db 100644
--- a/Modules/EAC.cs
+++ b/Modules/EAC.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.Generic;
using System.Linq;
+using AmongUs.GameOptions;
using HarmonyLib;
using Hazel;
using InnerNet;
+using UnityEngine;
using static EHR.Translator;
namespace EHR;
@@ -392,7 +395,7 @@ internal static class EAC
bool HasVent(int ventId) => ShipStatus.Instance.AllVents.Any(v => v.Id == ventId);
}
- private static void Report(PlayerControl pc, string reason)
+ internal static void Report(PlayerControl pc, string reason)
{
string msg = $"{pc.GetClientId()}|{pc.FriendCode}|{pc.Data.PlayerName}|{pc.GetClient().GetHashedPuid()}|{reason}";
//Cloud.SendData(msg);
@@ -692,4 +695,46 @@ internal static class StartGameHostPatchEAC
if (ShipStatus.Instance != null)
IsStartingAsHost = false;
}
+}
+
+[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
+static class CheckInvalidMovementPatch
+{
+ private static readonly Dictionary LastCheck = [];
+ public static readonly Dictionary LastPosition = [];
+ public static readonly HashSet ExemptedPlayers = [];
+
+ public static void Postfix(PlayerControl __instance)
+ {
+ if (!GameStates.IsInTask || !Options.EnableMovementChecking.GetBool() || Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod) >= 1.9f || AmongUsClient.Instance.Ping >= 300 || Utils.GetRegionName() is not ("EU" or "NA" or "AS") || __instance == null || __instance.PlayerId == 255) return;
+
+ var pos = __instance.Pos();
+ var now = Utils.TimeStamp;
+
+ if (!LastPosition.TryGetValue(__instance.PlayerId, out var lastPosition))
+ {
+ SetCurrentData();
+ return;
+ }
+
+ if (LastCheck[__instance.PlayerId] == now) return;
+
+ SetCurrentData();
+
+ if (ExemptedPlayers.Remove(__instance.PlayerId)) return;
+
+ if (Vector2.Distance(lastPosition, pos) > 10f && PhysicsHelpers.AnythingBetween(__instance.Collider, lastPosition, pos, Constants.ShipOnlyMask, false))
+ {
+ EAC.WarnHost();
+ EAC.Report(__instance, "This player is moving too fast, possibly using a speed hack.");
+ }
+
+ return;
+
+ void SetCurrentData()
+ {
+ LastPosition[__instance.PlayerId] = pos;
+ LastCheck[__instance.PlayerId] = now;
+ }
+ }
}
\ No newline at end of file
diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs
index 693bb3360..fd643badc 100644
--- a/Modules/ExtendedPlayerControl.cs
+++ b/Modules/ExtendedPlayerControl.cs
@@ -65,11 +65,9 @@ static class ExtendedPlayerControl
return CanUseVent(player, GetClosestVent(player)?.Id ?? int.MaxValue);
}
- // VentId is unused for now, but it can be used to block specific vents
- // ReSharper disable once UnusedParameter.Global
public static bool CanUseVent(this PlayerControl player, int ventId)
{
- return GameStates.IsInTask && (player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer || player.inVent);
+ return GameStates.IsInTask && (player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer || player.inVent) && Main.PlayerStates.Values.All(x => x.Role.CanUseVent(player, ventId));
}
// Next 3: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs
@@ -84,6 +82,15 @@ static class ExtendedPlayerControl
Vector2 playerpos = player.transform.position;
List vents = ShipStatus.Instance.AllVents.ToList();
vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position)));
+
+ if ((player.walkingToVent || player.inVent) && vents[0] != null)
+ {
+ var nextvents = vents[0].NearbyVents.ToList();
+ nextvents.RemoveAll(v => v == null);
+ nextvents.ForEach(v => vents.Remove(v));
+ vents.InsertRange(0, nextvents);
+ }
+
return vents;
}
@@ -300,17 +307,18 @@ static class ExtendedPlayerControl
// https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs
public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false)
{
- if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost) return;
+ if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null) return;
var playerId = player.PlayerId;
+ var playerClientId = player.GetClientId();
var playerRole = Utils.GetRoleMap(playerId).CustomRole;
var newRoleType = newCustomRole.GetRoleTypes();
RoleTypes remeberRoleType;
newRoleType = Options.CurrentGameMode switch
{
- CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => SpeedrunManager.CanKill.Contains(player.PlayerId) ? RoleTypes.Impostor : RoleTypes.Crewmate,
- CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(player.PlayerId), out var kvp) => kvp.Key switch
+ CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => SpeedrunManager.CanKill.Contains(playerId) ? RoleTypes.Impostor : RoleTypes.Crewmate,
+ CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out var kvp) => kvp.Key switch
{
CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor,
CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer,
@@ -325,6 +333,9 @@ static class ExtendedPlayerControl
var oldRoleIsDesync = playerRole.IsDesyncRole();
var newRoleIsDesync = newCustomRole.IsDesyncRole();
+ var newRoleVN = newCustomRole.GetVNRole();
+ var newRoleDY = newCustomRole.GetDYRole();
+
switch (oldRoleIsDesync, newRoleIsDesync)
{
// Desync role to normal role
@@ -334,13 +345,42 @@ static class ExtendedPlayerControl
{
var seerClientId = seer.GetClientId();
if (seerClientId == -1) continue;
+ var seerIsHost = seer.IsHost();
+ var self = player.PlayerId == seer.PlayerId;
- if (player.PlayerId != seer.PlayerId && seer.HasDesyncRole() && !seer.IsHost())
- remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist;
+ if (!self && seer.HasDesyncRole() && !seerIsHost)
+ remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist;
else remeberRoleType = newRoleType;
+ // Set role type for seer
StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole);
player.RpcSetRoleDesync(remeberRoleType, seerClientId);
+
+ if (self) continue;
+
+ (RoleTypes seerRoleType, CustomRoles seerCustomRole) = seer.GetRoleMap();
+ if (seer.IsAlive())
+ {
+ if (seerCustomRole.IsDesyncRole())
+ remeberRoleType = seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist;
+ else
+ remeberRoleType = seerRoleType;
+ }
+ else
+ {
+ var playerIsKiller = playerRole.IsImpostor();
+
+ remeberRoleType = RoleTypes.CrewmateGhost;
+ if (!playerIsKiller && seer.Is(Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost;
+
+ StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist : seerRoleType, seerCustomRole);
+ seer.RpcSetRoleDesync(remeberRoleType, playerClientId);
+ continue;
+ }
+
+ // Set role type for player
+ StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole);
+ seer.RpcSetRoleDesync(remeberRoleType, playerClientId);
}
break;
@@ -352,23 +392,43 @@ static class ExtendedPlayerControl
{
var seerClientId = seer.GetClientId();
if (seerClientId == -1) continue;
+ var self = player.PlayerId == seer.PlayerId;
- if (player.PlayerId == seer.PlayerId)
+ if (self)
{
remeberRoleType = player.IsHost() ? RoleTypes.Crewmate : RoleTypes.Impostor;
// For Desync Shapeshifter
- if (newCustomRole.GetDYRole() is RoleTypes.Shapeshifter)
+ if (newRoleDY is RoleTypes.Shapeshifter)
remeberRoleType = RoleTypes.Shapeshifter;
}
else
{
- if (!player.IsHost() && seer.HasDesyncRole()) remeberRoleType = newCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist;
- else remeberRoleType = newRoleType;
+ remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist;
}
StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole);
player.RpcSetRoleDesync(remeberRoleType, seerClientId);
+
+ if (self) continue;
+
+ var seerCustomRole = seer.GetRoleMap().CustomRole;
+ if (seer.IsAlive())
+ {
+ remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist;
+ }
+ else
+ {
+ remeberRoleType = RoleTypes.CrewmateGhost;
+
+ StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist, seerCustomRole);
+ seer.RpcSetRoleDesync(remeberRoleType, playerClientId);
+ continue;
+ }
+
+ // Set role type for player
+ StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole);
+ seer.RpcSetRoleDesync(remeberRoleType, playerClientId);
}
break;
@@ -399,17 +459,19 @@ static class ExtendedPlayerControl
{
foreach (var seer in Main.AllPlayerControls)
{
+ var seerData = seer.Data;
foreach (var target in Main.AllPlayerControls)
{
- if (StartGameHostPatch.RpcSetRoleReplacer.RoleMap.TryGetValue((seer.PlayerId, target.PlayerId), out var map))
- Logger.Info($"seer {seer.Data?.PlayerName}-{seer.PlayerId}, target {target.Data?.PlayerName}-{target.PlayerId} => {map.RoleType}, {map.CustomRole}", "Role Map");
+ var targetData = target.Data;
+ (RoleTypes roleType, CustomRoles customRole) = seer.GetRoleMap(targetData.PlayerId);
+ Logger.Info($"seer {seerData?.PlayerName}-{seerData?.PlayerId}, target {targetData.PlayerName}-{targetData.PlayerId} => {roleType}, {customRole}", "Role Map");
}
}
}
- Main.ChangedRole = true;
-
Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis");
+
+ Main.ChangedRole = true;
}
// https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/Utils.cs
@@ -1275,6 +1337,8 @@ static class ExtendedPlayerControl
if (target.Is(CustomRoles.Jackal))
Jackal.Instances.Do(x => x.PromoteSidekick());
+ Main.DiedThisRound.Add(target.PlayerId);
+
switch (killer.PlayerId == target.PlayerId)
{
case true when killer.shapeshifting:
diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs
index fba5fe045..5fd334805 100644
--- a/Modules/OptionHolder.cs
+++ b/Modules/OptionHolder.cs
@@ -126,10 +126,6 @@ public static class Options
"CamouflageMode.Default",
"CamouflageMode.Host",
"CamouflageMode.Karpe",
- "CamouflageMode.Lauryn",
- "CamouflageMode.Moe",
- "CamouflageMode.Pyro",
- "CamouflageMode.ryuk",
"CamouflageMode.Gurge44",
"CamouflageMode.TommyXL"
];
@@ -199,6 +195,7 @@ public static class Options
public static OptionItem ShowNKRemainOnEject;
public static OptionItem ShowTeamNextToRoleNameOnEject;
public static OptionItem CheatResponses;
+ public static OptionItem EnableMovementChecking;
public static OptionItem LowLoadMode;
public static OptionItem DeepLowLoad;
public static OptionItem DisableVoteBan;
@@ -1219,6 +1216,9 @@ public static class Options
CheatResponses = new StringOptionItem(19319, "CheatResponses", CheatResponsesName, 2, TabGroup.SystemSettings)
.SetHeader(true);
+ EnableMovementChecking = new BooleanOptionItem(19329, "EnableMovementChecking", true, TabGroup.SystemSettings)
+ .SetHeader(true);
+
DisableVoteBan = new BooleanOptionItem(19320, "DisableVoteBan", true, TabGroup.SystemSettings, true);
@@ -1362,11 +1362,9 @@ public static class Options
// Random Spawn
RandomSpawn = new BooleanOptionItem(22000, "RandomSpawn", false, TabGroup.GameSettings)
- .SetGameMode(CustomGameMode.Standard)
.SetColor(new Color32(19, 188, 233, byte.MaxValue));
AirshipAdditionalSpawn = new BooleanOptionItem(22010, "AirshipAdditionalSpawn", false, TabGroup.GameSettings)
- .SetParent(RandomSpawn)
- .SetGameMode(CustomGameMode.Standard);
+ .SetParent(RandomSpawn);
// Airship Variable Electrical
AirshipVariableElectrical = new BooleanOptionItem(22100, "AirshipVariableElectrical", false, TabGroup.GameSettings)
diff --git a/Modules/RPC.cs b/Modules/RPC.cs
index 4f76877db..57450ff7b 100644
--- a/Modules/RPC.cs
+++ b/Modules/RPC.cs
@@ -1390,7 +1390,6 @@ internal static class PlayerPhysicsRPCHandlerPatch
return false;
}
- Logger.Info($"{player.PlayerId}({(host ? "Host" : player.Data.PlayerName)}):{callId}({RPC.GetRpcName(callId)})", "PlayerPhysics_ReceiveRPC");
return true;
}
}
diff --git a/Modules/Utils.cs b/Modules/Utils.cs
index b140f313f..cee159ef8 100644
--- a/Modules/Utils.cs
+++ b/Modules/Utils.cs
@@ -97,6 +97,25 @@ public static class Utils
}
}
+ public static void CheckAndSetVentInteractions()
+ {
+ bool shouldPerformVentInteractions = false;
+
+ foreach (var pc in Main.AllPlayerControls)
+ {
+ if (VentilationSystemDeterioratePatch.BlockVentInteraction(pc))
+ {
+ VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id;
+ shouldPerformVentInteractions = true;
+ }
+ }
+
+ if (shouldPerformVentInteractions)
+ {
+ SetAllVentInteractions();
+ }
+ }
+
public static void TPAll(Vector2 location, bool log = true)
{
foreach (PlayerControl pc in Main.AllAlivePlayerControls)
@@ -124,6 +143,10 @@ public static class Utils
AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
if (log) Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} => {location}", "TP");
+
+ CheckInvalidMovementPatch.LastPosition[pc.PlayerId] = location;
+ CheckInvalidMovementPatch.ExemptedPlayers.Add(pc.PlayerId);
+
return true;
}
@@ -434,6 +457,8 @@ public static class Utils
var self = seerId == targetId || Main.PlayerStates[seerId].IsDead;
+ if (!self && Main.DiedThisRound.Contains(seerId)) return (string.Empty, Color.white);
+
bool isHnsAgentOverride = Options.CurrentGameMode == CustomGameMode.HideAndSeek && targetMainRole == CustomRoles.Agent && HnSManager.PlayerRoles[seerId].Interface.Team != Team.Impostor;
bool loversShowDifferentRole = false;
if (!GameStates.IsEnded && targetMainRole == CustomRoles.LovingImpostor && !self && seerMainRole != CustomRoles.LovingCrewmate && !seerSubRoles.Contains(CustomRoles.Lovers))
@@ -496,7 +521,6 @@ public static class Utils
RoleText = GetRoleString("Contagious-") + RoleText;
}
- // Bloodlust
if (targetSubRoles.Contains(CustomRoles.Bloodlust) && (self || pure || seeTargetBetrayalAddons))
{
RoleColor = GetRoleColor(CustomRoles.Bloodlust);
@@ -618,7 +642,7 @@ public static class Utils
StackFrame firstFrame = stFrames.FirstOrDefault();
var sb = new StringBuilder();
- sb.Append($" {ex.GetType().Name}: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------");
+ sb.Append($" {ex.GetType().Name}: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName.Split('\\')[^1]}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------");
bool skip = true;
foreach (StackFrame sf in stFrames)
@@ -638,10 +662,26 @@ public static class Utils
}
sb.Append("\n------ End of Method Stack Trace ------");
- sb.Append("\n------ Exception Stack Trace ------\n ");
+ sb.Append("\n------ Exception ------\n ");
sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n "));
+ sb.Append("\n------ End of Exception ------");
+ sb.Append("\n------ Exception Stack Trace ------\n");
+
+ var stEx = new StackTrace(ex, true);
+ var stFramesEx = stEx.GetFrames();
+
+ foreach (StackFrame sf in stFramesEx)
+ {
+ var callerMethod = sf.GetMethod();
+
+ string callerMethodName = callerMethod?.Name;
+ string callerClassName = callerMethod?.DeclaringType?.FullName;
+
+ sb.Append($"\n at {callerClassName}.{callerMethodName} in {sf.GetFileName()}, line {sf.GetFileLineNumber()}");
+ }
+
sb.Append("\n------ End of Exception Stack Trace ------");
Logger.Error(sb.ToString(), firstFrame?.GetMethod()?.ToString(), multiLine: true);
@@ -861,7 +901,6 @@ public static class Utils
pc.Is(CustomRoles.NiceSwapper) ||
pc.Is(CustomRoles.Speedrunner) ||
pc.Is(CustomRoles.Needy) ||
- pc.Is(CustomRoles.Lazy) ||
pc.Is(CustomRoles.Loyal) ||
pc.Is(CustomRoles.SuperStar) ||
pc.Is(CustomRoles.CyberStar) ||
@@ -874,7 +913,7 @@ public static class Utils
{
if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush) return false;
if (__instance.PlayerId == PlayerControl.LocalPlayer.PlayerId || Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.SoloKombat or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato or CustomGameMode.Speedrun || (Options.CurrentGameMode == CustomGameMode.HideAndSeek && HnSManager.IsRoleTextEnabled(PlayerControl.LocalPlayer, __instance)) || Main.VisibleTasksCount && PlayerControl.LocalPlayer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool() || PlayerControl.LocalPlayer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) return true;
- if (Altruist.On && Main.DiedThisRound.Contains(PlayerControl.LocalPlayer.PlayerId)) return false;
+ if (CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(PlayerControl.LocalPlayer.PlayerId)) return false;
switch (__instance.GetCustomRole())
{
@@ -1598,19 +1637,26 @@ public static class Utils
public static void CheckAndSpawnAdditionalRefugee(NetworkedPlayerInfo deadPlayer)
{
- if (Options.CurrentGameMode != CustomGameMode.Standard || deadPlayer == null || deadPlayer.Object.Is(CustomRoles.Refugee) || Main.HasJustStarted || !GameStates.InGame || !Options.SpawnAdditionalRefugeeOnImpsDead.GetBool() || Main.AllAlivePlayerControls.Length < Options.SpawnAdditionalRefugeeMinAlivePlayers.GetInt() || CustomRoles.Refugee.RoleExist(countDead: true) || Main.AllAlivePlayerControls == null || Main.AllAlivePlayerControls.Length == 0 || Main.AllAlivePlayerControls.Any(x => x.PlayerId != deadPlayer.PlayerId && (x.Is(CustomRoleTypes.Impostor) || (x.IsNeutralKiller() && !Options.SpawnAdditionalRefugeeWhenNKAlive.GetBool())))) return;
-
- PlayerControl[] ListToChooseFrom = Main.AllAlivePlayerControls.Where(x => x.PlayerId != deadPlayer.PlayerId && x.Is(CustomRoleTypes.Crewmate) && !x.Is(CustomRoles.Loyal)).ToArray();
-
- if (ListToChooseFrom.Length > 0)
+ try
{
- var pc = ListToChooseFrom.RandomElement();
- pc.RpcSetCustomRole(CustomRoles.Refugee);
- pc.SetKillCooldown();
- Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Madmate);
- Logger.Warn($"{pc.GetRealName()} is now a Refugee since all Impostors are dead", "Add Refugee");
+ if (Options.CurrentGameMode != CustomGameMode.Standard || deadPlayer == null || deadPlayer.Object.Is(CustomRoles.Refugee) || Main.HasJustStarted || !GameStates.InGame || !Options.SpawnAdditionalRefugeeOnImpsDead.GetBool() || Main.AllAlivePlayerControls.Length < Options.SpawnAdditionalRefugeeMinAlivePlayers.GetInt() || CustomRoles.Refugee.RoleExist(countDead: true) || Main.AllAlivePlayerControls == null || Main.AllAlivePlayerControls.Length == 0 || Main.AllAlivePlayerControls.Any(x => x.PlayerId != deadPlayer.PlayerId && (x.Is(CustomRoleTypes.Impostor) || (x.IsNeutralKiller() && !Options.SpawnAdditionalRefugeeWhenNKAlive.GetBool())))) return;
+
+ PlayerControl[] ListToChooseFrom = Main.AllAlivePlayerControls.Where(x => x.PlayerId != deadPlayer.PlayerId && x.Is(CustomRoleTypes.Crewmate) && !x.Is(CustomRoles.Loyal)).ToArray();
+
+ if (ListToChooseFrom.Length > 0)
+ {
+ var pc = ListToChooseFrom.RandomElement();
+ pc.RpcSetCustomRole(CustomRoles.Refugee);
+ pc.SetKillCooldown();
+ Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Madmate);
+ Logger.Warn($"{pc.GetRealName()} is now a Refugee since all Impostors are dead", "Add Refugee");
+ }
+ else Logger.Msg("No Player to change to Refugee.", "Add Refugee");
+ }
+ catch (Exception e)
+ {
+ ThrowException(e);
}
- else Logger.Msg("No Player to change to Refugee.", "Add Refugee");
}
public static void SendMessage(string text, byte sendTo = byte.MaxValue, string title = "", bool noSplit = false)
@@ -2033,10 +2079,10 @@ public static class Utils
? SeerRealName
: seerTeam switch
{
- Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
Team.Impostor => $"\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
- Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
- Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
+ Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? longInfo : seer.GetRoleInfo()) + mHelp}",
_ => SeerRealName
};
}
@@ -2229,7 +2275,7 @@ public static class Utils
? $"{target.GetDisplayRoleName(seeTargetBetrayalAddons: shouldSeeTargetAddons)}{GetProgressText(target)}\r\n"
: string.Empty;
- if (Altruist.On && Main.DiedThisRound.Contains(seer.PlayerId)) TargetRoleText = string.Empty;
+ if (CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(seer.PlayerId)) TargetRoleText = string.Empty;
if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush) TargetRoleText = string.Empty;
if (!GameStates.IsLobby)
diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs
index 537570016..e02c1de02 100644
--- a/Patches/ChatCommandPatch.cs
+++ b/Patches/ChatCommandPatch.cs
@@ -309,7 +309,7 @@ internal static class ChatCommands
Main.Instance.StartCoroutine(Countdown());
return;
- System.Collections.IEnumerator Countdown()
+ IEnumerator Countdown()
{
var timer = 30f;
while (timer > 0f)
@@ -330,7 +330,7 @@ internal static class ChatCommands
{
ReadyPlayers.Add(player.PlayerId);
}
-
+
private static void DraftStartCommand(ChatController __instance, PlayerControl player, string text, string[] args)
{
DraftResult = [];
@@ -344,7 +344,7 @@ internal static class ChatCommands
return;
}
- DraftRoles = allRoles.Shuffle().Partition(allPlayerIds.Length).Zip(allPlayerIds).ToDictionary(x => x.Second, x => x.First.ToList());
+ DraftRoles = allRoles.Shuffle().Partition(allPlayerIds.Length).Zip(allPlayerIds).ToDictionary(x => x.Second, x => x.First.Take(5).ToList());
foreach ((byte id, List roles) in DraftRoles)
{
@@ -2187,4 +2187,4 @@ internal class RpcSendChatPatch
__result = true;
return false;
}
-}
+}
\ No newline at end of file
diff --git a/Patches/ClientPatch.cs b/Patches/ClientPatch.cs
index b70c5063d..72f3b4775 100644
--- a/Patches/ClientPatch.cs
+++ b/Patches/ClientPatch.cs
@@ -124,10 +124,6 @@ internal class SetResolutionManager
{
public static void Postfix()
{
- //if (MainMenuManagerPatch.qqButton != null)
- // MainMenuManagerPatch.qqButton.transform.localPosition = Vector3.Reflect(MainMenuManagerPatch.template.transform.localPosition, Vector3.left);
- //if (MainMenuManagerPatch.discordButton != null)
- // MainMenuManagerPatch.discordButton.transform.localPosition = Vector3.Reflect(MainMenuManagerPatch.template.transform.localPosition, Vector3.left);
if (MainMenuManagerPatch.UpdateButton != null)
MainMenuManagerPatch.UpdateButton.transform.localPosition = MainMenuManagerPatch.Template.transform.localPosition + new Vector3(0.25f, 0.75f);
}
@@ -143,15 +139,14 @@ internal class InnerNetObjectSerializePatch
}
}
+[HarmonyPatch(typeof(InnerNetClient))]
public static class InnerNetClientPatch
{
- private static byte Timer;
-
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendInitialData))]
[HarmonyPrefix]
public static bool SendInitialDataPrefix(InnerNetClient __instance, int clientId)
{
- if (!Constants.IsVersionModded() || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
+ if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return true;
// We make sure other stuff like PlayerControl and LobbyBehavior is spawned properly
// Then we spawn the networked data for new clients
MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
@@ -161,10 +156,10 @@ public static class InnerNetClientPatch
List obj = __instance.allObjects;
lock (obj)
{
- System.Collections.Generic.HashSet hashSet = [];
+ HashSet hashSet = new();
for (int i = 0; i < __instance.allObjects.Count; i++)
{
- InnerNetObject innerNetObject = __instance.allObjects[i];
+ InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
if (innerNetObject && (innerNetObject.OwnerId != -4 || __instance.AmModdedHost) && hashSet.Add(innerNetObject.gameObject))
{
GameManager gameManager = innerNetObject as GameManager;
@@ -189,23 +184,26 @@ public static class InnerNetClientPatch
return false;
}
+ // InnerSloth vanilla officials send PlayerInfo in spilt reliable packets
private static void DelaySpawnPlayerInfo(InnerNetClient __instance, int clientId)
{
- var players = GameData.Instance.AllPlayers.ToArray();
+ var players = GameData.Instance.AllPlayers.ToArray().ToList();
- // We send 5 players at a time to prevent too large packets
- foreach (var batch in players.Chunk(5))
+ foreach (var player in players)
{
- MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
- messageWriter.StartMessage(6);
- messageWriter.Write(__instance.GameId);
- messageWriter.WritePacked(clientId);
+ if (player != null && player.ClientId != clientId && !player.Disconnected)
+ {
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(6);
+ messageWriter.Write(__instance.GameId);
+ messageWriter.WritePacked(clientId);
- batch.DoIf(p => p != null && p.ClientId != clientId && !p.Disconnected, p => __instance.WriteSpawnMessage(p, p.OwnerId, p.SpawnFlags, messageWriter));
+ __instance.WriteSpawnMessage(player, player.OwnerId, player.SpawnFlags, messageWriter);
+ messageWriter.EndMessage();
- messageWriter.EndMessage();
- __instance.SendOrDisconnect(messageWriter);
- messageWriter.Recycle();
+ __instance.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
}
}
@@ -221,7 +219,7 @@ public static class InnerNetClientPatch
{
for (int i = 0; i < __instance.allObjects.Count; i++)
{
- InnerNetObject innerNetObject = __instance.allObjects[i];
+ InnerNetObject innerNetObject = __instance.allObjects[i]; // False error
if (innerNetObject && innerNetObject is not NetworkedPlayerInfo && innerNetObject.IsDirty && (innerNetObject.AmOwner || (innerNetObject.OwnerId == -2 && __instance.AmHost)))
{
MessageWriter messageWriter = __instance.Streams[(int)innerNetObject.sendMode];
@@ -229,8 +227,14 @@ public static class InnerNetClientPatch
messageWriter.WritePacked(innerNetObject.NetId);
try
{
- if (innerNetObject.Serialize(messageWriter, false)) messageWriter.EndMessage();
- else messageWriter.CancelMessage();
+ if (innerNetObject.Serialize(messageWriter, false))
+ {
+ messageWriter.EndMessage();
+ }
+ else
+ {
+ messageWriter.CancelMessage();
+ }
if (innerNetObject.Chunked && innerNetObject.IsDirty)
{
@@ -266,62 +270,50 @@ public static class InnerNetClientPatch
[HarmonyPostfix]
public static void FixedUpdatePostfix(InnerNetClient __instance)
{
- if (!__instance.AmHost || __instance.Streams == null || __instance.NetworkMode != NetworkModes.OnlineGame) return;
+ // Send it with None calls. Who cares?
+ if (!Constants.IsVersionModded() || GameStates.IsInGame || __instance.NetworkMode != NetworkModes.OnlineGame) return;
+ if (!__instance.AmHost || __instance.Streams == null) return;
- if (Timer == 0)
+ var players = GameData.Instance.AllPlayers.ToArray();
+ foreach (var player in players)
{
- Timer = 1;
- return;
- }
-
- var player = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.IsDirty);
- if (player != null)
- {
- Timer = 0;
- MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
- messageWriter.StartMessage(5);
- messageWriter.Write(__instance.GameId);
- messageWriter.StartMessage(1);
- messageWriter.WritePacked(player.NetId);
- try
+ if (player.IsDirty)
{
- if (player.Serialize(messageWriter, false))
+ MessageWriter messageWriter = MessageWriter.Get();
+ messageWriter.StartMessage(5);
+ messageWriter.Write(__instance.GameId);
+ messageWriter.StartMessage(1);
+ messageWriter.WritePacked(player.NetId);
+ try
{
+ if (player.Serialize(messageWriter, false))
+ {
+ messageWriter.EndMessage();
+ }
+ else
+ {
+ messageWriter.CancelMessage();
+ player.ClearDirtyBits();
+ continue;
+ }
+
messageWriter.EndMessage();
+ __instance.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
}
- else
+ catch (Exception ex)
{
+ Logger.Exception(ex, "FixedUpdatePostfix");
messageWriter.CancelMessage();
player.ClearDirtyBits();
- return;
}
-
- messageWriter.EndMessage();
- __instance.SendOrDisconnect(messageWriter);
- messageWriter.Recycle();
- }
- catch (Exception ex)
- {
- Logger.Exception(ex, "FixedUpdatePostfix");
- messageWriter.CancelMessage();
- player.ClearDirtyBits();
}
}
}
}
-[HarmonyPatch(typeof(GameData), nameof(GameData.DirtyAllData))]
-internal class DirtyAllDataPatch
-{
- // Currently, this function only occurs in CreatePlayer.
- // It's believed to lag the host, delay the PlayerControl spawn message, blackout new clients
- // and send huge packets to all clients while there's completely no need to run this.
- // Temporarily disable it until Innersloth gets a better fix.
- public static bool Prefix() => false;
-}
-
[HarmonyPatch]
-internal class AuthTimeoutPatch
+internal static class AuthTimeoutPatch
{
[HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
[HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
@@ -339,7 +331,7 @@ internal class AuthTimeoutPatch
return false;
}
- // If you don't patch this, you still need to wait for 5s.
+ // If you don't patch this, you still need to wait for 5 s.
// I have no idea why this is happening
[HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__1), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__1.MoveNext))]
[HarmonyPrefix]
diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs
index f66c81106..694dec29e 100644
--- a/Patches/ExilePatch.cs
+++ b/Patches/ExilePatch.cs
@@ -108,7 +108,7 @@ static class ExileControllerWrapUpPatch
PetsPatch.RpcRemovePet(pc);
}
- if (Options.RandomSpawn.GetBool() || Options.CurrentGameMode != CustomGameMode.Standard)
+ if (Options.RandomSpawn.GetBool())
{
RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch
{
@@ -122,13 +122,8 @@ static class ExileControllerWrapUpPatch
if (map != null) Main.AllAlivePlayerControls.Do(map.RandomTeleport);
}
- Utils.CheckAndSpawnAdditionalRefugee(exiled);
-
FallFromLadder.Reset();
Utils.CountAlivePlayers(true);
- Utils.AfterMeetingTasks();
- Utils.SyncAllSettings();
- Utils.NotifyRoles(ForceLoop: true);
}
static void WrapUpFinalizer()
@@ -140,7 +135,7 @@ static class ExileControllerWrapUpPatch
{
AntiBlackout.SendGameData();
AntiBlackout.SetRealPlayerRoles();
- }, 1.1f, "Restore IsDead Task");
+ }, 0.7f, "Restore IsDead Task");
LateTask.New(() =>
{
Main.AfterMeetingDeathPlayers.Do(x =>
@@ -157,6 +152,10 @@ static class ExileControllerWrapUpPatch
});
Main.AfterMeetingDeathPlayers.Clear();
AntiBlackout.ResetAfterMeeting();
+ Utils.AfterMeetingTasks();
+ Utils.SyncAllSettings();
+ Utils.NotifyRoles(NoCache: true);
+ Utils.CheckAndSetVentInteractions();
}, 1.2f, "AfterMeetingDeathPlayers Task");
}
diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs
index d644ed1f5..e2e9e985b 100644
--- a/Patches/IntroPatch.cs
+++ b/Patches/IntroPatch.cs
@@ -850,7 +850,7 @@ static class IntroCutsceneDestroyPatch
Main.PlayerStates[lp.PlayerId].SetDead();
}
- if (Options.RandomSpawn.GetBool() || Options.CurrentGameMode != CustomGameMode.Standard)
+ if (Options.RandomSpawn.GetBool())
{
RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch
{
@@ -878,20 +878,7 @@ static class IntroCutsceneDestroyPatch
}
}
- bool shouldPerformVentInteractions = false;
- foreach (var pc in Main.AllPlayerControls)
- {
- if (VentilationSystemDeterioratePatch.BlockVentInteraction(pc))
- {
- VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id;
- shouldPerformVentInteractions = true;
- }
- }
-
- if (shouldPerformVentInteractions)
- {
- Utils.SetAllVentInteractions();
- }
+ Utils.CheckAndSetVentInteractions();
if (AFKDetector.ActivateOnStart.GetBool())
{
diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs
index ec684ef26..3a983ba42 100644
--- a/Patches/MeetingHudPatch.cs
+++ b/Patches/MeetingHudPatch.cs
@@ -288,12 +288,13 @@ static class CheckForEndVotingPatch
__instance.RpcVotingComplete(states.ToArray(), exiledPlayer, tie);
CheckForDeathOnExile(PlayerState.DeathReason.Vote, exileId);
+ Utils.CheckAndSpawnAdditionalRefugee(exiledPlayer);
Main.LastVotedPlayerInfo = exiledPlayer;
- AntiBlackout.ExilePlayerId = exiledPlayer.PlayerId;
+ AntiBlackout.ExilePlayerId = exileId;
if (Main.LastVotedPlayerInfo != null)
{
- AntiBlackout.ExilePlayerId = exiledPlayer.PlayerId;
+ AntiBlackout.ExilePlayerId = exileId;
ConfirmEjections(Main.LastVotedPlayerInfo, braked);
}
@@ -501,8 +502,8 @@ static class CheckForEndVotingPatch
{
try
{
- Witch.OnCheckForEndVoting(deathReason, playerIds);
- Virus.OnCheckForEndVoting(deathReason, playerIds);
+ if (Witch.playerIdList.Count > 0) Witch.OnCheckForEndVoting(deathReason, playerIds);
+ if (Virus.playerIdList.Count > 0) Virus.OnCheckForEndVoting(deathReason, playerIds);
if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds);
foreach (var playerId in playerIds)
{
@@ -975,26 +976,22 @@ static class MeetingHudStartPatch
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))]
static class MeetingHudUpdatePatch
{
- private static int bufferTime = 10;
+ private static int BufferTime = 10;
- private static void ClearShootButton(MeetingHud __instance, bool forceAll = false)
- => __instance.playerStates.ToList().ForEach(x =>
- {
- if ((forceAll || !Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && x.transform.FindChild("ShootButton") != null) Object.Destroy(x.transform.FindChild("ShootButton").gameObject);
- });
+ private static void ClearShootButton(MeetingHud __instance, bool forceAll = false) => __instance.playerStates.DoIf(
+ x => (forceAll || !Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && x.transform.FindChild("ShootButton") != null,
+ x => Object.Destroy(x.transform.FindChild("ShootButton").gameObject));
public static void Postfix(MeetingHud __instance)
{
try
{
- // Meeting Skip with vote counting on keystroke (m + delete)
+ // Meeting Skip with vote counting on keystroke (F6)
if (AmongUsClient.Instance.AmHost && Input.GetKeyDown(KeyCode.F6))
{
__instance.CheckForEndVoting();
}
- //if (Options.DisableCrackedGlass.GetBool()) __instance.CrackedGlass = null;
-
if (AmongUsClient.Instance.AmHost && Input.GetMouseButtonUp(1) && Input.GetKey(KeyCode.LeftControl))
{
__instance.playerStates.DoIf(x => x.HighlightedFX.enabled, x =>
@@ -1006,7 +1003,7 @@ static class MeetingHudUpdatePatch
player.RpcExileV2();
Main.PlayerStates[player.PlayerId].SetDead();
Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName));
- Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}を処刑しました", "Execution");
+ Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} was executed by the host", "Execution");
__instance.CheckForEndVoting();
}
});
@@ -1022,10 +1019,10 @@ static class MeetingHudUpdatePatch
return;
}
- bufferTime--;
- if (bufferTime < 0 && __instance.discussionTimer > 0)
+ BufferTime--;
+ if (BufferTime < 0 && __instance.discussionTimer > 0)
{
- bufferTime = 10;
+ BufferTime = 10;
var myRole = PlayerControl.LocalPlayer.GetCustomRole();
__instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out var ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true));
@@ -1059,7 +1056,7 @@ static class MeetingHudUpdatePatch
}
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.SetHighlighted))]
-class SetHighlightedPatch
+static class SetHighlightedPatch
{
public static bool Prefix(PlayerVoteArea __instance, bool value)
{
@@ -1071,7 +1068,7 @@ class SetHighlightedPatch
}
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))]
-class MeetingHudOnDestroyPatch
+static class MeetingHudOnDestroyPatch
{
public static void Postfix()
{
@@ -1088,7 +1085,7 @@ class MeetingHudOnDestroyPatch
}
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))]
-class MeetingHudCastVotePatch
+static class MeetingHudCastVotePatch
{
private static readonly Dictionary ShouldCancelVoteList = [];
diff --git a/Patches/MovingPlatformBehaviourPatch.cs b/Patches/MovingPlatformBehaviourPatch.cs
index bea440199..9795a6d5b 100644
--- a/Patches/MovingPlatformBehaviourPatch.cs
+++ b/Patches/MovingPlatformBehaviourPatch.cs
@@ -7,31 +7,35 @@ namespace EHR;
[HarmonyPatch(typeof(MovingPlatformBehaviour))]
public static class MovingPlatformBehaviourPatch
{
- private static bool isDisabled;
+ private static bool IsDisabled;
[HarmonyPatch(nameof(MovingPlatformBehaviour.Start)), HarmonyPrefix]
public static void StartPrefix(MovingPlatformBehaviour __instance)
{
- isDisabled = Options.DisableAirshipMovingPlatform.GetBool();
+ IsDisabled = Options.DisableAirshipMovingPlatform.GetBool();
- if (isDisabled)
+ if (IsDisabled)
{
__instance.transform.localPosition = __instance.DisabledPosition;
ShipStatus.Instance.Cast().outOfOrderPlat.SetActive(true);
}
}
+
[HarmonyPatch(nameof(MovingPlatformBehaviour.IsDirty), MethodType.Getter), HarmonyPrefix]
public static bool GetIsDirtyPrefix(ref bool __result)
{
- if (isDisabled)
+ if (IsDisabled)
{
__result = false;
return false;
}
+
return true;
}
+
[HarmonyPatch(nameof(MovingPlatformBehaviour.Use), typeof(PlayerControl)), HarmonyPrefix]
- public static bool UsePrefix() => !isDisabled;
+ public static bool UsePrefix() => !IsDisabled;
+
[HarmonyPatch(nameof(MovingPlatformBehaviour.SetSide)), HarmonyPrefix]
- public static bool SetSidePrefix() => !isDisabled;
-}
+ public static bool SetSidePrefix() => !IsDisabled;
+}
\ No newline at end of file
diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs
index 40cee53a3..fe28fff1f 100644
--- a/Patches/OnGameStartedPatch.cs
+++ b/Patches/OnGameStartedPatch.cs
@@ -255,14 +255,18 @@ internal class ChangeRoleSettings
Camouflage.BlockCamouflage = false;
Camouflage.Init();
- var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).Select(p => $"{p.name}").ToArray();
- if (invalidColor.Length > 0)
+
+ if (AmongUsClient.Instance.AmHost)
{
- var msg = GetString("Error.InvalidColor");
- Logger.SendInGame(msg);
- msg += "\n" + string.Join(",", invalidColor);
- Utils.SendMessage(msg);
- Logger.Error(msg, "CoStartGame");
+ var invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).Select(p => $"{p.name}").ToArray();
+ if (invalidColor.Length > 0)
+ {
+ var msg = GetString("Error.InvalidColor");
+ Logger.SendInGame(msg);
+ msg += "\n" + string.Join(",", invalidColor);
+ Utils.SendMessage(msg);
+ Logger.Error(msg, "CoStartGame");
+ }
}
RoleResult = [];
@@ -1194,4 +1198,4 @@ static class FixIntroPatch
DestroyableSingleton.Instance.HideGameLoader();
}, 1f, log: false);
}
-}
+}
\ No newline at end of file
diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs
index 3afc83555..8af6476e9 100644
--- a/Patches/PlayerControlPatch.cs
+++ b/Patches/PlayerControlPatch.cs
@@ -994,7 +994,7 @@ static class ReportDeadBodyPatch
}
catch (Exception e)
{
- Utils.ThrowException(e);
+ ThrowException(e);
}
return true;
@@ -1229,7 +1229,7 @@ static class FixedUpdatePatch
}
}
- if (Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive() && (!Altruist.On || Main.PlayerStates[id].Role is not Altruist at || at.ReviveStartTS == 0))
+ if (Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive() && (!Altruist.On || !CustomRoles.Altruist.RoleExist() || Main.PlayerStates[id].Role is not Altruist at || at.ReviveStartTS == 0))
{
DeadBufferTime.TryAdd(id, 30);
DeadBufferTime[id]--;
diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs
index 45982594b..f701fec60 100644
--- a/Patches/PlayerJoinAndLeftPatch.cs
+++ b/Patches/PlayerJoinAndLeftPatch.cs
@@ -495,4 +495,18 @@ static class InnerNetClientFixedUpdatePatch
AmongUsClient.Instance.KickNotJoinedPlayers();
}
+}
+
+[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))]
+static class RpcSetColorPatch
+{
+ public static void Postfix(PlayerControl __instance, byte bodyColor)
+ {
+ if (Main.IntroDestroyed || __instance == null) return;
+
+ Logger.Info($"{__instance.GetRealName()}'s color is {Palette.GetColorName(bodyColor)}", "RpcSetColor");
+ if (bodyColor == 255) return;
+
+ Main.PlayerColors[__instance.PlayerId] = Palette.PlayerColors[bodyColor];
+ }
}
\ No newline at end of file
diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs
index c5e25ada7..5389f051d 100644
--- a/Patches/RandomSpawnPatch.cs
+++ b/Patches/RandomSpawnPatch.cs
@@ -43,7 +43,7 @@ class RandomSpawn
player.RpcResetAbilityCooldown();
if (Options.FixFirstKillCooldown.GetBool() && !MeetingStates.MeetingCalled) player.SetKillCooldown(Main.AllPlayerKillCooldown[player.PlayerId]);
else if (Options.StartingKillCooldown.GetInt() != 10) player.SetKillCooldown(Options.StartingKillCooldown.GetInt());
- if (!Options.RandomSpawn.GetBool() && Options.CurrentGameMode == CustomGameMode.Standard) return;
+ if (!Options.RandomSpawn.GetBool()) return;
new AirshipSpawnMap().RandomTeleport(player);
Penguin.OnSpawnAirship();
}
diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs
index f9cf36585..c7176e9f4 100644
--- a/Patches/ShipStatusPatch.cs
+++ b/Patches/ShipStatusPatch.cs
@@ -375,6 +375,9 @@ static class ShipStatusSpawnPlayerPatch
{
if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true;
+ // Lazy doesn't teleport to the meeting table
+ if (player.Is(CustomRoles.Lazy)) return false;
+
Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers));
Vector2 position = __instance.MeetingSpawnCenter + direction * __instance.SpawnRadius + new Vector2(0.0f, 0.3636f);
@@ -394,6 +397,9 @@ static class PolusShipStatusSpawnPlayerPatch
{
if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true;
+ // Lazy doesn't teleport to the meeting table
+ if (player.Is(CustomRoles.Lazy)) return false;
+
int num1 = Mathf.FloorToInt(numPlayers / 2f);
int num2 = player.PlayerId % 15;
@@ -455,6 +461,7 @@ static class VentilationSystemDeterioratePatch
{
public static Dictionary LastClosestVent = [];
private static readonly Dictionary LastCanUseVent = [];
+ private static readonly Dictionary LastClosestVentForUpdate = [];
public static void Postfix(VentilationSystem __instance)
{
@@ -598,7 +605,14 @@ static class VentilationSystemDeterioratePatch
public static void CheckVentInteraction(PlayerControl pc)
{
- if (!GameStates.IsInTask) return;
+ if (!GameStates.IsInTask || ExileController.Instance) return;
+
+ int closestVent = pc.GetClosestVent().Id;
+ if (!LastClosestVentForUpdate.TryGetValue(pc.PlayerId, out int lastClosestVent))
+ {
+ LastClosestVentForUpdate[pc.PlayerId] = closestVent;
+ return;
+ }
bool canUse = pc.CanUseVent();
@@ -608,10 +622,28 @@ static class VentilationSystemDeterioratePatch
return;
}
- if (couldUse != canUse)
+ if (couldUse != canUse || lastClosestVent != closestVent)
{
LastCanUseVent[pc.PlayerId] = canUse;
SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast(), pc);
}
}
+}
+
+[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.IsVentCurrentlyBeingCleaned))]
+static class VentSystemIsVentCurrentlyBeingCleanedPatch
+{
+ // Patch block use vent for host becouse host always skips RpcSerializeVent
+ public static bool Prefix([HarmonyArgument(0)] int id, ref bool __result)
+ {
+ if (!AmongUsClient.Instance.AmHost) return true;
+
+ if (!PlayerControl.LocalPlayer.CanUseVent(id))
+ {
+ __result = true;
+ return false;
+ }
+
+ return true;
+ }
}
\ No newline at end of file
diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs
index 0490e4d5d..883162bf1 100644
--- a/Patches/TaskAssignPatch.cs
+++ b/Patches/TaskAssignPatch.cs
@@ -5,6 +5,7 @@ using EHR.Impostor;
using EHR.Modules;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
+using Il2CppSystem.Collections.Generic;
using Random = UnityEngine.Random;
namespace EHR;
@@ -13,7 +14,7 @@ namespace EHR;
class AddTasksFromListPatch
{
public static void Prefix( /*ShipStatus __instance,*/
- [HarmonyArgument(4)] Il2CppSystem.Collections.Generic.List unusedTasks)
+ [HarmonyArgument(4)] List unusedTasks)
{
if (!AmongUsClient.Instance.AmHost) return;
@@ -157,8 +158,8 @@ class RpcSetTasksPatch
NumShortTasks = Options.MadSnitchTasks.GetInt();
}
- // GM and Lazy have no tasks
- if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.Needy) || Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA || pc.Is(CustomRoles.Lazy))
+ // GM and Lazy Guy have no tasks
+ if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.Needy) || Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters)
{
hasCommonTasks = false;
NumShortTasks = 0;
@@ -188,7 +189,7 @@ class RpcSetTasksPatch
// List containing IDs of assignable tasks
// Clone of the second argument of the original RpcSetTasks
- Il2CppSystem.Collections.Generic.List TasksList = new();
+ List TasksList = new();
foreach (var num in taskTypeIds)
{
TasksList.Add(num);
@@ -204,18 +205,18 @@ class RpcSetTasksPatch
// HashSet where assigned tasks will be placed
// Prevent multiple assignments of the same task
- Il2CppSystem.Collections.Generic.HashSet usedTaskTypes = new();
+ HashSet usedTaskTypes = new();
int start2 = 0;
int start3 = 0;
// List of assignable long tasks
- Il2CppSystem.Collections.Generic.List LongTasks = new();
+ List LongTasks = new();
foreach (var task in ShipStatus.Instance.LongTasks)
LongTasks.Add(task);
Shuffle(LongTasks);
// List of assignable short tasks
- Il2CppSystem.Collections.Generic.List ShortTasks = new();
+ List ShortTasks = new();
foreach (var task in ShipStatus.Instance.ShortTasks)
ShortTasks.Add(task);
Shuffle(ShortTasks);
@@ -244,7 +245,7 @@ class RpcSetTasksPatch
}
}
- private static void Shuffle(Il2CppSystem.Collections.Generic.List list)
+ private static void Shuffle(List list)
{
for (int i = 0; i < list.Count - 1; i++)
{
diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json
index 859551242..b5f4ef680 100644
--- a/Resources/Lang/en_US.json
+++ b/Resources/Lang/en_US.json
@@ -25,10 +25,10 @@
"ShortTeamName.Impostor": "Imp",
"ShortTeamName.Crewmate": "Crew",
"ShortTeamName.Neutral": "Neut",
- "YouAreCrewmate": "You are Crewmate",
- "YouAreImpostor": "You are Impostor",
- "YouAreNeutral": "You are Neutral",
- "YouAreMadmate": "You are in Impostors team",
+ "YouAreCrewmate": "You are a Crewmate",
+ "YouAreImpostor": "You are an Impostor",
+ "YouAreNeutral": "You are a Neutral",
+ "YouAreMadmate": "You are in the Impostor team",
"CrewmateSub": "Find and exile the Impostors",
"ImpostorSub": "Sabotage and kill everyone",
"NeutralSub": "Work alone to achieve your victory",
@@ -229,7 +229,6 @@
"Pestilence": "Pestilence",
"Poisoner": "Poisoner",
"Sidekick": "Sidekick",
- "NWitch": "Controller",
"Totocalcio": "Follower",
"Succubus": "Cultist",
"HexMaster": "Hex Master",
@@ -513,7 +512,6 @@
"PestilenceInfo": "You're unstoppable",
"PoisonerInfo": "Your kills are delayed",
"SidekickInfo": "Help the Jackal win",
- "NWitchInfo": "Control crewmates and live to see them lose",
"TotocalcioInfo": "Follow a player and help them",
"SuccubusInfo": "Charm everyone",
"HexMasterInfo": "Hex players to kill them in meetings",
@@ -810,7 +808,7 @@
"RascalInfoLong": "(Add-ons):\\nAs the Rascal, you can die to the Sheriff, and Snitch can find you if Snitch can find madmates.\\n\\nOnly assigned to Crewmates, cannot be assigned by the Merchant.",
"SoullessInfoLong": "(Add-ons):\\nWhen a Cursed Soul snatches your soul, you get this add-on.\\n\\nYou are not counted as alive.",
"GravestoneInfoLong": "(Add-ons):\\nAs the Gravestone, your role is revealed to everyone when you die.",
- "LazyInfoLong": "(Add-ons):\\nAs the Lazy, you are assigned a single short task and are immune to Warlocks, Puppeteers, and Gangsters.",
+ "LazyInfoLong": "(Add-ons):\\nAs the Lazy, you are too lazy to walk to the meeting table when meetings are called, so you start the next round exactly where you ended the previous one, and you're also immune to Warlocks, Puppeteers, and Gangsters.",
"AutopsyInfoLong": "(Add-ons):\\nAs the Autopsy, you can see how people died.\\n\\nCannot be assigned to Doctor, Tracefinder, Scientist, or Sunnyboy.",
"LoyalInfoLong": "(Add-ons):\\nAs the Loyal, you cannot be recruited by roles such as Jackal or Cultist.\\n\\nCannot be assigned to neutrals.",
"EvilSpiritInfoLong": "(Add-ons):\\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.",
@@ -1006,6 +1004,7 @@
"GameStateCommand.ShowTasks": "Show amount of completed tasks",
"MinPlayersForGameStateCommand": "Minimum alive players to use this command",
"EveryoneSeesDeathReasons": "Everyone Sees Death Reasons",
+ "BlockVentMovement": "Block Movement Between Vents (while in a vent)",
"DoorsResetMode": "Mode",
"AllClosed": "Close All",
"AllOpen": "Open All",
@@ -2606,7 +2605,6 @@
"MinerTeleButtonText": "Warp",
"ArsonistDouseButtonText": "Douse",
"PuppeteerOperateButtonText": "Manipulate",
- "WitchControlButtonText": "Control",
"WarlockShapeshiftButtonText": "Spell",
"BountyHunterChangeButtonText": "Swap",
"EvilTrackerChangeButtonText": "Track",
@@ -2734,6 +2732,7 @@
"CountdownText": "Re-Entering Lobby in {0}s",
"EAC.CheatDetected.HighLevel": "Warning: EAC detected High Level of cheats.",
"EAC.CheatDetected.LowLevel": "Warning: EAC detected Low Level of cheats. One of the players is hacking.",
+ "EnableMovementChecking": "Enable EAC Checks for Invalid Movement (against speed hacks) (may cause false positives)",
"DisableVoteBan": "Disable Vote-Kick (prevent hackers from banning everyone)",
"PlayerIsRole": "{0} was {1}.",
"PlayerExiled": "{0} was ejected...",
@@ -2814,11 +2813,7 @@
"FungleChance": "Chance that the map is The Fungle",
"CamouflageMode.Default": "Default",
"CamouflageMode.Host": "Host",
- "CamouflageMode.Karpe": "KARPED1EM",
- "CamouflageMode.Lauryn": "Lauryn",
- "CamouflageMode.Moe": "Moe",
- "CamouflageMode.Pyro": "Pyro",
- "CamouflageMode.ryuk": "ryuk",
+ "CamouflageMode.Karpe": "KARPED1EM",
"CamouflageMode.Gurge44": "Gurge44",
"CamouflageMode.TommyXL": "TommyXL",
"pet_Bedcrab": "Bedcrab",
@@ -4566,7 +4561,6 @@
"AmnesiacInvalidTarget": "Remember failed",
"RememberedNeutralKiller": "You remembered you were a neutral killer!",
"RememberedMaverick": "You remembered you were a Maverick!",
- "RememberedWitch": "You remembered you were a Witch!",
"RememberedPursuer": "You remembered you were a Pursuer!",
"RememberedTotocalcio": "You remembered you were a Follower!",
"RememberedAmnesiac": "You failed to remember your role.",
@@ -5003,7 +4997,6 @@
"AdditionalWinnerRoleText.Lawyer": "The Lawyer wins along",
"AdditionalWinnerRoleText.FFF": "The Hater killed a recruit",
"AdditionalWinnerRoleText.Provocateur": "The Provocateur wins along",
- "AdditionalWinnerRoleText.NWitch": "The Witch survived",
"AdditionalWinnerRoleText.Sunnyboy": "And there's endless sunshine!",
"AdditionalWinnerRoleText.Totocalcio": "The Follower wins along",
"AdditionalWinnerRoleText.Pursuer": "The Pursuer survived",
diff --git a/Roles/Crewmate/President.cs b/Roles/Crewmate/President.cs
index 8a93b90df..e45c7a473 100644
--- a/Roles/Crewmate/President.cs
+++ b/Roles/Crewmate/President.cs
@@ -209,7 +209,11 @@ namespace EHR.Crewmate
public override bool OnVote(PlayerControl voter, PlayerControl target)
{
if (voter.PlayerId != PresidentId || !IsRecruiting || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false;
- if (!target.Is(CustomRoleTypes.Crewmate) || target.IsConverted()) return true;
+ if (!target.Is(CustomRoleTypes.Crewmate) || target.IsConverted())
+ {
+ IsRecruiting = false;
+ return true;
+ }
CustomRoles role = GovernmentRecruitRoles[DecreeSettings[Decree.GovernmentRecruiting][1].GetValue()];
if (role.IsDesyncRole() && target.GetRoleTypes() != role.GetDYRole()) role = CustomRoles.NiceGuesser;
diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs
index cc9a3a5f8..8fd68d00b 100644
--- a/Roles/Crewmate/Sheriff.cs
+++ b/Roles/Crewmate/Sheriff.cs
@@ -7,29 +7,28 @@ namespace EHR.Crewmate;
public class Sheriff : RoleBase
{
private const int Id = 8800;
- public static List playerIdList = [];
+ private static List PlayerIdList = [];
- public static OptionItem KillCooldown;
+ private static OptionItem KillCooldown;
private static OptionItem MisfireKillsTarget;
- public static OptionItem ShotLimitOpt;
- public static OptionItem ShowShotLimit;
+ private static OptionItem ShotLimitOpt;
+ private static OptionItem ShowShotLimit;
private static OptionItem CanKillAllAlive;
- public static OptionItem CanKillNeutrals;
- public static OptionItem CanKillNeutralsMode;
- public static OptionItem CanKillMadmate;
- public static OptionItem CanKillCharmed;
- public static OptionItem CanKillLovers;
- public static OptionItem CanKillSidekicks;
- public static OptionItem CanKillEgoists;
- public static OptionItem CanKillContagious;
- public static OptionItem SidekickSheriffCanGoBerserk;
- public static OptionItem SetNonCrewCanKill;
- public static OptionItem NonCrewCanKillCrew;
- public static OptionItem NonCrewCanKillImp;
- public static OptionItem NonCrewCanKillNeutral;
+ private static OptionItem CanKillNeutrals;
+ private static OptionItem CanKillNeutralsMode;
+ private static OptionItem CanKillMadmate;
+ private static OptionItem CanKillCharmed;
+ private static OptionItem CanKillLovers;
+ private static OptionItem CanKillSidekicks;
+ private static OptionItem CanKillContagious;
+ private static OptionItem SidekickSheriffCanGoBerserk;
+ private static OptionItem SetNonCrewCanKill;
+ private static OptionItem NonCrewCanKillCrew;
+ private static OptionItem NonCrewCanKillImp;
+ private static OptionItem NonCrewCanKillNeutral;
public static OptionItem KeepsGameGoing;
public static OptionItem UsePet;
- public static Dictionary KillTargetOptions = [];
+ private static readonly Dictionary KillTargetOptions = [];
public static readonly string[] KillOption =
[
@@ -37,7 +36,7 @@ public class Sheriff : RoleBase
"SheriffCanKillSeparately"
];
- public override bool IsEnable => playerIdList.Count > 0;
+ public override bool IsEnable => PlayerIdList.Count > 0;
public override void SetupCustomOption()
{
@@ -53,7 +52,6 @@ public class Sheriff : RoleBase
CanKillCharmed = new BooleanOptionItem(Id + 22, "SheriffCanKillCharmed", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
CanKillLovers = new BooleanOptionItem(Id + 24, "SheriffCanKillLovers", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
CanKillSidekicks = new BooleanOptionItem(Id + 23, "SheriffCanKillSidekick", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
- CanKillEgoists = new BooleanOptionItem(Id + 25, "SheriffCanKillEgoist", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
CanKillContagious = new BooleanOptionItem(Id + 27, "SheriffCanKillContagious", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
CanKillNeutrals = new BooleanOptionItem(Id + 16, "SheriffCanKillNeutrals", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]);
CanKillNeutralsMode = new StringOptionItem(Id + 14, "SheriffCanKillNeutralsMode", KillOption, 0, TabGroup.CrewmateRoles).SetParent(CanKillNeutrals);
@@ -90,12 +88,12 @@ public class Sheriff : RoleBase
public override void Init()
{
- playerIdList = [];
+ PlayerIdList = [];
}
public override void Add(byte playerId)
{
- playerIdList.Add(playerId);
+ PlayerIdList.Add(playerId);
playerId.SetAbilityUseLimit(ShotLimitOpt.GetInt());
Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()} : Shot Limit - {playerId.GetAbilityUseLimit()}", "Sheriff");
@@ -153,7 +151,6 @@ public class Sheriff : RoleBase
CustomRoles.Charmed => CanKillCharmed.GetBool(),
CustomRoles.Lovers => CanKillLovers.GetBool(),
CustomRoles.Recruit => CanKillSidekicks.GetBool(),
- CustomRoles.Egoist => CanKillEgoists.GetBool(),
CustomRoles.Contagious => CanKillContagious.GetBool(),
CustomRoles.Rascal => true,
_ => false
@@ -172,4 +169,9 @@ public class Sheriff : RoleBase
}
};
}
+
+ public override string GetProgressText(byte playerId, bool comms)
+ {
+ return ShowShotLimit.GetBool() ? base.GetProgressText(playerId, comms) : Utils.GetTaskCount(playerId, comms);
+ }
}
\ No newline at end of file
diff --git a/Roles/Impostor/Assassin.cs b/Roles/Impostor/Assassin.cs
index c2d674f62..8d7c2d3c5 100644
--- a/Roles/Impostor/Assassin.cs
+++ b/Roles/Impostor/Assassin.cs
@@ -139,6 +139,13 @@ internal class Assassin : RoleBase
public override bool OnVanish(PlayerControl pc)
{
if (pc == null || !pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false;
+
+ if (!IsUndertaker)
+ {
+ LateTask.New(() => Take(pc), 1.5f, "Ninja Vanish");
+ return true;
+ }
+
Take(pc);
return false;
}
@@ -148,10 +155,9 @@ internal class Assassin : RoleBase
if (MarkedPlayer != byte.MaxValue)
{
var target = Utils.GetPlayerById(MarkedPlayer);
- if (IsUndertaker) target.TP(pc);
- else pc.TP(target);
+ var tpSuccess = IsUndertaker ? target.TP(pc) : pc.TP(target);
- if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && pc.RpcCheckAndMurder(target))
+ if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && tpSuccess && pc.RpcCheckAndMurder(target))
{
MarkedPlayer = byte.MaxValue;
SendRPC(pc.PlayerId);
@@ -159,6 +165,7 @@ internal class Assassin : RoleBase
pc.SyncSettings();
pc.SetKillCooldown(DefaultKillCooldown);
}
+ else if (!tpSuccess) pc.Notify(GetString("TargetCannotBeTeleported"));
}
}
diff --git a/Roles/Impostor/Camouflager.cs b/Roles/Impostor/Camouflager.cs
index 14c5c04d6..abe0cf9ec 100644
--- a/Roles/Impostor/Camouflager.cs
+++ b/Roles/Impostor/Camouflager.cs
@@ -63,7 +63,7 @@ namespace EHR.Impostor
return true;
}
- if (pc.GetAbilityUseLimit() < 1)
+ if (pc.GetAbilityUseLimit() < 1 && !Options.DisableShapeshiftAnimations.GetBool())
{
pc.SetKillCooldown(CamouflageDuration.GetFloat() + 1f);
}
diff --git a/Roles/Impostor/Disperser.cs b/Roles/Impostor/Disperser.cs
index 86d0a2186..f2cf22486 100644
--- a/Roles/Impostor/Disperser.cs
+++ b/Roles/Impostor/Disperser.cs
@@ -60,7 +60,7 @@ public class Disperser : RoleBase
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
if (shapeshifter == null || (!shapeshifting && !UseUnshiftTrigger.GetBool())) return false;
- if (shapeshifter.GetAbilityUseLimit() < 1)
+ if (shapeshifter.GetAbilityUseLimit() < 1 && !DisableShapeshiftAnimations.GetBool())
{
shapeshifter.SetKillCooldown(DisperserShapeshiftDuration.GetFloat() + 1f);
return false;
diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs
index e6a015617..77bebe26b 100644
--- a/Roles/Neutral/Amnesiac.cs
+++ b/Roles/Neutral/Amnesiac.cs
@@ -15,11 +15,10 @@ public class Amnesiac : RoleBase
private static OptionItem RememberCooldown;
private static OptionItem CanRememberCrewPower;
private static OptionItem IncompatibleNeutralMode;
- private static OptionItem CanVent;
public static OptionItem RememberMode;
public static OptionItem RoleBasis;
- public static readonly CustomRoles[] AmnesiacIncompatibleNeutralMode =
+ private static readonly CustomRoles[] AmnesiacIncompatibleNeutralMode =
[
CustomRoles.Amnesiac,
CustomRoles.Pursuer,
@@ -29,8 +28,8 @@ public class Amnesiac : RoleBase
private static readonly string[] RememberModes =
[
- "AmnesiacRM.ByKillButton",
- "AmnesiacRM.ByReportingBody"
+ "AmnesiacRM.ByReportingBody",
+ "AmnesiacRM.ByKillButton"
];
private byte AmnesiacId;
@@ -43,14 +42,12 @@ public class Amnesiac : RoleBase
RememberMode = new StringOptionItem(Id + 9, "RememberMode", RememberModes, 0, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]);
RememberCooldown = new FloatOptionItem(Id + 10, "RememberCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles)
- .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac])
+ .SetParent(RememberMode)
.SetValueFormat(OptionFormat.Seconds);
CanRememberCrewPower = new BooleanOptionItem(Id + 11, "CanRememberCrewPower", false, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]);
IncompatibleNeutralMode = new StringOptionItem(Id + 12, "IncompatibleNeutralMode", AmnesiacIncompatibleNeutralMode.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true)
.SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]);
- CanVent = new BooleanOptionItem(Id + 13, "CanVent", false, TabGroup.NeutralRoles)
- .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]);
RoleBasis = new StringOptionItem(Id + 14, "AmnesiacRoleBasis", [CustomRoles.Engineer.ToColoredString(), CustomRoles.Crewmate.ToColoredString()], 1, TabGroup.NeutralRoles, noTranslation: true)
.SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]);
}
@@ -69,7 +66,6 @@ public class Amnesiac : RoleBase
public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat();
public override bool CanUseKillButton(PlayerControl player) => !player.Data.IsDead && RememberMode.GetValue() == 0;
- public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool();
public override void ApplyGameOptions(IGameOptions opt, byte playerId) => opt.SetVision(false);
public static void OnAnyoneDeath(PlayerControl target)
diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs
index 05c77f91a..6abdebb66 100644
--- a/Roles/Neutral/Bandit.cs
+++ b/Roles/Neutral/Bandit.cs
@@ -87,11 +87,7 @@ public class Bandit : RoleBase
for (int i = 0; i < AllSubRoles.Count; i++)
{
var role = AllSubRoles[i];
- if (role == CustomRoles.Cleansed || // making Bandit unable to steal Cleansed for obvious reasons. Although it can still be cleansed by cleanser.
- role == CustomRoles.LastImpostor ||
- role == CustomRoles.Lovers || // Causes issues involving Lovers Suicide
- (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) ||
- (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()))
+ if (IsBlacklistedAddon(role))
{
Logger.Info($"Removed {role} from stealable addons", "Bandit");
AllSubRoles.Remove(role);
@@ -108,6 +104,17 @@ public class Bandit : RoleBase
return addon;
}
+ private static bool IsBlacklistedAddon(CustomRoles role) => (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) || (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()) || StartGameHostPatch.BasisChangingAddons.ContainsKey(role) || role is
+ CustomRoles.Egoist or
+ CustomRoles.Workhorse or
+ CustomRoles.Cleansed or
+ CustomRoles.Busy or
+ CustomRoles.Lovers or
+ CustomRoles.Stressed or
+ CustomRoles.Lazy or
+ CustomRoles.Rascal or
+ CustomRoles.LastImpostor;
+
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (!On) return true;
diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs
index 9206f624e..78a08c2ac 100644
--- a/Roles/Neutral/Glitch.cs
+++ b/Roles/Neutral/Glitch.cs
@@ -288,11 +288,8 @@ public class Glitch : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
- Logger.Test("pos0");
if (!hud || seer == null || !seer.IsAlive() || meeting) return string.Empty;
- Logger.Test("pos1");
if (Main.PlayerStates[seer.PlayerId].Role is not Glitch gc) return string.Empty;
- Logger.Test("pos2");
var sb = new StringBuilder();
diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs
index 5e5c5f199..a289e35dc 100644
--- a/Roles/Neutral/Jester.cs
+++ b/Roles/Neutral/Jester.cs
@@ -13,6 +13,7 @@ namespace EHR.Neutral
public static OptionItem SunnyboyChance;
public static OptionItem VentCooldown;
public static OptionItem MaxInVentTime;
+ public static OptionItem BlockVentMovement;
public override bool IsEnable => On;
public override void SetupCustomOption()
@@ -22,14 +23,16 @@ namespace EHR.Neutral
.SetParent(CustomRoleSpawnChances[CustomRoles.Jester]);
JesterCanVent = new BooleanOptionItem(10911, "CanVent", false, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jester]);
+ VentCooldown = new FloatOptionItem(10913, "VentCooldown", new(0f, 60f, 0.5f), 0f, TabGroup.NeutralRoles)
+ .SetParent(JesterCanVent)
+ .SetValueFormat(OptionFormat.Seconds);
+ MaxInVentTime = new FloatOptionItem(10914, "MaxInVentTime", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles)
+ .SetParent(JesterCanVent)
+ .SetValueFormat(OptionFormat.Seconds);
+ BlockVentMovement = new BooleanOptionItem(10916, "BlockVentMovement", false, TabGroup.NeutralRoles)
+ .SetParent(JesterCanVent);
JesterHasImpostorVision = new BooleanOptionItem(10912, "ImpostorVision", false, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jester]);
- VentCooldown = new FloatOptionItem(10913, "VentCooldown", new(0f, 60f, 0.5f), 0f, TabGroup.NeutralRoles)
- .SetParent(CustomRoleSpawnChances[CustomRoles.Jester])
- .SetValueFormat(OptionFormat.Seconds);
- MaxInVentTime = new FloatOptionItem(10914, "MaxInVentTime", new(0f, 900f, 0.5f), 900f, TabGroup.NeutralRoles)
- .SetParent(CustomRoleSpawnChances[CustomRoles.Jester])
- .SetValueFormat(OptionFormat.Seconds);
SunnyboyChance = new IntegerOptionItem(10915, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jester])
.SetValueFormat(OptionFormat.Percent);
@@ -51,5 +54,7 @@ namespace EHR.Neutral
AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat();
opt.SetVision(JesterHasImpostorVision.GetBool());
}
+
+ public override bool CanUseVent(PlayerControl pc, int ventId) => !BlockVentMovement.GetBool() || pc.GetClosestVent()?.Id == ventId;
}
}
\ No newline at end of file
diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs
index 22ffe4377..9c6f58b45 100644
--- a/Roles/Neutral/Virus.cs
+++ b/Roles/Neutral/Virus.cs
@@ -11,7 +11,7 @@ namespace EHR.Neutral
public class Virus : RoleBase
{
private const int Id = 13200;
- private static List playerIdList = [];
+ public static List playerIdList = [];
public static List InfectedPlayer = [];
public static Dictionary VirusNotify = [];
public static List InfectedBodies = [];
diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs
index 91d31e5cc..30e0f29ee 100644
--- a/Roles/RoleBase.cs
+++ b/Roles/RoleBase.cs
@@ -53,7 +53,12 @@ namespace EHR
public virtual bool CanUseImpostorVentButton(PlayerControl pc)
{
- return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || Amnesiac.WasAmnesiac.Contains(pc.PlayerId) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.CanVent.GetBool())) && Circumvent.CanUseImpostorVentButton(pc) && pc.Data.Role.Role is not RoleTypes.Engineer;
+ return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || Amnesiac.WasAmnesiac.Contains(pc.PlayerId) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.CanVent.GetBool())) && Circumvent.CanUseImpostorVentButton(pc);
+ }
+
+ public virtual bool CanUseVent(PlayerControl pc, int ventId)
+ {
+ return true;
}
public virtual bool CanUseSabotage(PlayerControl pc)