Merge remote-tracking branch 'origin/main'

This commit is contained in:
Gurge44 committed 2026-01-23 09:12:28 +01:00
commit c7cfd4ce28
36 files changed
+668 -136

No files matched your search

+62 -19
View File
@@ -3,31 +3,74 @@ name: Build
on:
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build:
if: github.head_ref != 'translation'
# ---------------------------------------------------------
# 1) Build EHR
# ---------------------------------------------------------
build-ehr:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Build EHR
run: dotnet build EHR.csproj -c Release -o build/
- name: Restore dependencies
run: dotnet restore
- name: Upload EHR artifact
uses: actions/upload-artifact@v4
with:
name: ehr
path: build/EHR.dll
retention-days: 1
- name: Build
run: dotnet build --configuration Release --no-restore
# ---------------------------------------------------------
# 2) Build Control Panel
# ---------------------------------------------------------
build-control-panel:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Restore .NET packages
run: dotnet restore -p:SelfContained=true CTA/CustomTeamAssigner/CustomTeamAssigner.csproj
- name: Build Control Panel (MSBuild)
run: |
msbuild CTA/CustomTeamAssigner/CustomTeamAssigner.csproj `
/p:Configuration=Release `
/p:Platform="x64" `
/p:PublishSingleFile=true `
/p:SelfContained=true `
/p:RuntimeIdentifier=win-x64 `
/p:IncludeAllContentForSelfExtract=true `
/p:DebugType=None `
/p:PublishDir=build\ `
/t:publish
- name: Rename exe file
run: |
mkdir build/
mv CTA/CustomTeamAssigner/build/CustomTeamAssigner.exe build/EHR_Control-Panel_BETA.exe
- name: Upload Control Panel artifact
uses: actions/upload-artifact@v4
with:
name: control-panel
path: build/EHR_Control-Panel_BETA.exe
retention-days: 1
+2
View File
@@ -289,6 +289,7 @@ public enum CustomRoles
// Neutrals
Accumulator,
Agitator,
Amnesiac,
Amogus,
@@ -358,6 +359,7 @@ public enum CustomRoles
Pulse,
Pursuer,
Pyromaniac,
Quarry,
QuizMaster,
Reckless,
Revolutionist,
+3 -3
View File
@@ -327,12 +327,12 @@ public static class RoomRush
MapNames.Airship => SystemTypes.MainHall,
MapNames.Fungle => SystemTypes.Dropship,
(MapNames)6 => (SystemTypes)SubmergedCompatibility.SubmergedSystemTypes.UpperCentral,
_ => throw new ArgumentOutOfRangeException(map.ToString(), "Invalid map")
_ => AllRooms.RandomElement()
};
DonePlayers.Clear();
RoomGoal = AllRooms.Without(previous).RandomElement();
Vector2 goalPos = Map.Positions.GetValueOrDefault(RoomGoal, RoomGoal.GetRoomClass().transform.position);
Vector2 goalPos = RoomGoal.GetRoomClass().transform.position;
float speed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
int time;
@@ -579,7 +579,7 @@ public static class RoomRush
if (WinByPointsInsteadOfDeaths.GetBool())
{
Vector2 location = everyoneDies ? Map.Positions.GetValueOrDefault(RoomGoal, RoomGoal.GetRoomClass().transform.position) : DonePlayers.RandomElement().GetPlayer().Pos();
Vector2 location = everyoneDies && !Main.LIMap ? Map.Positions.GetValueOrDefault(RoomGoal, RoomGoal.GetRoomClass().transform.position) : DonePlayers.RandomElement().GetPlayer().Pos();
playersOutsideRoom.MassTP(location);
}
else
+5 -1
View File
@@ -279,7 +279,7 @@ public class Main : BasePlugin
public static MapNames CurrentMap => (MapNames)NormalOptions.MapId;
public static bool LIMap => NormalOptions?.MapId == 7;
public static bool LIMap => NormalOptions is { MapId: 7 };
public override void Load()
{
@@ -561,6 +561,8 @@ public class Main : BasePlugin
{ CustomRoles.Thanos, "#F9D401" },
{ CustomRoles.Berserker, "#50538F" },
{ CustomRoles.SerialKiller, "#233fcc" },
{ CustomRoles.Quarry, "#c1fb2b" },
{ CustomRoles.Accumulator, "#2bfbae" },
{ CustomRoles.Spider, "#C9E44C" },
{ CustomRoles.SoulCollector, "#6021A0" },
{ CustomRoles.Sharpshooter, "#5901D4" },
@@ -1066,6 +1068,8 @@ public enum CustomWinner
Necromancer = CustomRoles.Necromancer,
Wraith = CustomRoles.Wraith,
SerialKiller = CustomRoles.SerialKiller,
Quarry = CustomRoles.Quarry,
Accumulator = CustomRoles.Accumulator,
Spider = CustomRoles.Spider,
SoulCollector = CustomRoles.SoulCollector,
Berserker = CustomRoles.Berserker,
+3
View File
@@ -84,6 +84,9 @@ public static class Camouflage
{
if (Camouflager.On && Camouflager.IsActive) return true;
if (Options.CurrentGameMode != CustomGameMode.Standard) return false;
if (MeetingStates.FirstMeeting && Options.CommsCamouflagePreventRound1.GetBool()) return false;
switch (Main.CurrentMap)
{
case MapNames.Fungle when Options.CommsCamouflageDisableOnFungle.GetBool():
+1
View File
@@ -368,6 +368,7 @@ namespace EHR
System.Collections.IEnumerator WaitForMeetingEnd()
{
yield return new WaitForSecondsRealtime(5f);
while (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield return null;
yield return new WaitForSecondsRealtime(1f);
while (ReportDeadBodyPatch.MeetingStarted || GameStates.IsMeeting || ExileController.Instance || AntiBlackout.SkipTasks) yield return null;
+6
View File
@@ -377,6 +377,7 @@ internal static class CustomRolesHelper
CustomRoles.Clerk => CustomRoles.Crewmate,
CustomRoles.CovenMember => CustomRoles.Crewmate,
CustomRoles.Augur => Augur.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate,
CustomRoles.Accumulator => Accumulator.CanVentBeforeKilling.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate,
// Vanilla roles (just in case)
CustomRoles.ImpostorEHR => CustomRoles.Impostor,
@@ -516,6 +517,7 @@ internal static class CustomRolesHelper
CustomRoles.Duality => RoleTypes.Impostor,
CustomRoles.Berserker => RoleTypes.Impostor,
CustomRoles.SerialKiller => RoleTypes.Impostor,
CustomRoles.Quarry => RoleTypes.Shapeshifter,
CustomRoles.SoulCollector => RoleTypes.Impostor,
CustomRoles.Spider => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter,
CustomRoles.Explosivist => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter,
@@ -654,6 +656,8 @@ internal static class CustomRolesHelper
CustomRoles.Parasite or
CustomRoles.Berserker or
CustomRoles.SerialKiller or
CustomRoles.Quarry or
CustomRoles.Accumulator or
CustomRoles.Spider or
CustomRoles.SoulCollector or
CustomRoles.Sharpshooter or
@@ -1964,6 +1968,8 @@ public enum CountTypes
HexMaster,
Wraith,
SerialKiller,
Quarry,
Accumulator,
Spider,
SoulCollector,
Berserker,
+1 -1
View File
@@ -282,7 +282,7 @@ public class CustomRpcSender
throw new InvalidOperationException(errorMsg);
}
if (messages >= 10)
if (messages >= AmongUsClient.Instance.GetMaxMessagePackingLimit())
{
EndMessage(startNew: true);
StartMessage(currentRpcTarget);
+1 -1
View File
@@ -237,7 +237,7 @@ public static class CollectionExtensions
/// <typeparam name="T">The type of the elements in the collection</typeparam>
/// <returns><c>true</c> if the collection contains any elements that satisfy the predicate, <c>false</c> otherwise</returns>
[Annotations.CollectionAccess(Annotations.CollectionAccessType.Read)]
public static bool FindFirst<T>(this IEnumerable<T> collection, Func<T, bool> predicate, out T element)
public static bool FindFirst<T>(this IEnumerable<T> collection, [Annotations.InstantHandle] Func<T, bool> predicate, out T element)
{
if (collection is List<T> list)
{
+13 -3
View File
@@ -217,6 +217,7 @@ public static class GuessManager
switch (pc.GetCustomRole())
{
case CustomRoles.Augur when !((Augur)Main.PlayerStates[pc.PlayerId].Role).CanGuess:
case CustomRoles.Augur when Main.GuesserGuessed[pc.PlayerId] >= Augur.MaxGuessesPerGame.GetInt():
case CustomRoles.Augur when Main.GuesserGuessedMeeting[pc.PlayerId] >= Augur.MaxGuessesPerMeeting.GetInt():
case CustomRoles.NiceGuesser when Main.GuesserGuessed[pc.PlayerId] >= Options.GGCanGuessTime.GetInt():
@@ -485,7 +486,8 @@ public static class GuessManager
Doomsayer.GuessesCountPerMeeting++;
if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt()) Doomsayer.CantGuess = true;
if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt())
Doomsayer.CantGuess = true;
if (!Doomsayer.KillCorrectlyGuessedPlayers.GetBool() && pc.PlayerId != dp.PlayerId)
{
@@ -518,6 +520,13 @@ public static class GuessManager
}
}
if (pc.Is(CustomRoles.Augur) && pc.PlayerId == dp.PlayerId)
{
ShowMessage("Augur.IncorrectGuess");
((Augur)Main.PlayerStates[pc.PlayerId].Role).CanGuess = false;
return true;
}
string name = dp.GetRealName();
if (!Options.DisableKillAnimationOnGuess.GetBool()) CustomSoundsManager.RPCPlayCustomSoundAll("Gunfire");
@@ -537,7 +546,8 @@ public static class GuessManager
Doomsayer.GuessingToWin[pc.PlayerId]++;
Doomsayer.SendRPC(pc);
if (!Doomsayer.GuessedRoles.Contains(role)) Doomsayer.GuessedRoles.Add(role);
if (!Doomsayer.GuessedRoles.Contains(role))
Doomsayer.GuessedRoles.Add(role);
Doomsayer.CheckCountGuess(pc);
}
@@ -1362,7 +1372,7 @@ public static class GuessManager
int textIndex = 0;
int messages = 0;
int packingLimit = 10;
int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit();
var skipped = false;
PlayerControl guesser = guesserId.GetPlayer();
+1
View File
@@ -209,6 +209,7 @@ public static class NameColorManager
CustomRoles.Crewpostor when seer.Is(CustomRoleTypes.Impostor) && Options.AlliesKnowCrewpostor.GetBool() => Main.RoleColors[CustomRoles.Madmate],
CustomRoles.Hypocrite when seer.Is(CustomRoleTypes.Impostor) && Hypocrite.AlliesKnowHypocrite.GetBool() => Main.RoleColors[CustomRoles.Madmate],
CustomRoles.President when ((President)targetRoleClass).IsRevealed => Main.RoleColors[CustomRoles.President],
CustomRoles.Quarry when ((Quarry)targetRoleClass).TargetId == seer.PlayerId => "000000",
_ => color
};
+9
View File
@@ -614,6 +614,7 @@ public static class Options
public static OptionItem CommsCamouflageLimitMaxTimesPerGame;
public static OptionItem CommsCamouflageLimitMaxTimesPerRound;
public static OptionItem CommsCamouflageSetSameSpeed;
public static OptionItem CommsCamouflagePreventRound1;
public static OptionItem DisableReportWhenCC;
public static OptionItem SabotageTimeControl;
public static OptionItem SkeldReactorTimeLimit;
@@ -743,6 +744,7 @@ public static class Options
public static OptionItem EnableAutoMessage;
public static OptionItem AutoMessageSendInterval;
public static OptionItem DraftMaxRolesPerPlayer;
public static OptionItem DraftAffectedByRoleSpawnChances;
public static OptionItem LargerRoleTextSize;
public static OptionItem DynamicTaskCountColor;
public static OptionItem ShowTaskCountWhenAlive;
@@ -2116,6 +2118,10 @@ public static class Options
CommsCamouflageSetSameSpeed = new BooleanOptionItem(22211, "CommsCamouflageSetSameSpeed", true, TabGroup.GameSettings)
.SetGameMode(CustomGameMode.Standard)
.SetColor(new Color32(243, 96, 96, byte.MaxValue));
CommsCamouflagePreventRound1 = new BooleanOptionItem(22212, "CommsCamouflagePreventRound1", true, TabGroup.GameSettings)
.SetGameMode(CustomGameMode.Standard)
.SetColor(new Color32(243, 96, 96, byte.MaxValue));
DisableReportWhenCC = new BooleanOptionItem(22300, "DisableReportWhenCC", false, TabGroup.GameSettings)
.SetGameMode(CustomGameMode.Standard)
@@ -3013,6 +3019,9 @@ public static class Options
DraftMaxRolesPerPlayer = new IntegerOptionItem(19431, "DraftMaxRolesPerPlayer", new(1, 30, 1), 5, TabGroup.GameSettings)
.SetGameMode(CustomGameMode.Standard)
.SetColor(new Color32(193, 255, 209, byte.MaxValue));
DraftAffectedByRoleSpawnChances = new BooleanOptionItem(19435, "DraftAffectedByRoleSpawnChances", false, TabGroup.GameSettings)
.SetColor(new Color32(193, 255, 209, byte.MaxValue));
LargerRoleTextSize = new BooleanOptionItem(24451, "LargerRoleTextSize", false, TabGroup.GameSettings)
.SetColor(new Color32(193, 255, 209, byte.MaxValue));
+5 -16
View File
@@ -50,7 +50,6 @@ public enum CustomRPC
SyncAbilityCD,
SyncGeneralOptions,
SyncRoleData,
RequestSendMessage,
NotificationPopper,
RequestCommandProcessing,
@@ -72,10 +71,10 @@ public enum CustomRPC
SetDrawPlayer,
SyncHeadHunter,
SyncRabbit,
SyncSoulHunter,
BAU = 150,
SyncSoulHunter,
SyncMycologist,
SyncBubble,
AddTornado,
@@ -88,10 +87,10 @@ public enum CustomRPC
SyncMafiosoData,
SyncMafiosoPistolCD,
SyncDamoclesTimer,
SyncChronomancer,
Sicko = 164,
SyncChronomancer,
PenguinSync,
SyncInfection,
SetAlchemistPotion,
@@ -135,6 +134,7 @@ public enum CustomRPC
SyncVengefulRomanticTarget,
SetRevealedPlayer,
SetCurrentRevealTarget,
RpcPassBomb,
SetDoomsayerProgress = 209,
/*
@@ -147,7 +147,6 @@ public enum CustomRPC
*/
SetTrackerTarget = 215,
RpcPassBomb,
SetAlchemistTimer,
SyncPostman,
SyncChangeling,
@@ -168,6 +167,7 @@ public enum CustomRPC
SyncCamouflage,
SetChatVisible,
Exclusionary,
Deadlined,
// Game Modes
RoomRushDataSync,
@@ -255,7 +255,7 @@ internal static class RPCHandlerPatch
{
if (id == 115) return true;
if (SubmergedCompatibility.IsSubmerged() && id is >= 120 and <= 124) return true;
return (CustomRPC)id is CustomRPC.VersionCheck or CustomRPC.RequestRetryVersionCheck or CustomRPC.AntiBlackout or CustomRPC.SyncNameNotify or CustomRPC.RequestSendMessage or CustomRPC.RequestCommandProcessing or CustomRPC.Judge or CustomRPC.SetSwapperVotes or CustomRPC.MeetingKill or CustomRPC.Guess or CustomRPC.NemesisRevenge or CustomRPC.BAU or CustomRPC.FFAKill or CustomRPC.TMGSync or CustomRPC.InspectorCommand or CustomRPC.ImitatorClick or CustomRPC.RetributionistClick or CustomRPC.StarspawnClick or CustomRPC.VentriloquistClick;
return (CustomRPC)id is CustomRPC.VersionCheck or CustomRPC.RequestRetryVersionCheck or CustomRPC.AntiBlackout or CustomRPC.SyncNameNotify or CustomRPC.RequestCommandProcessing or CustomRPC.Judge or CustomRPC.SetSwapperVotes or CustomRPC.MeetingKill or CustomRPC.Guess or CustomRPC.NemesisRevenge or CustomRPC.BAU or CustomRPC.FFAKill or CustomRPC.TMGSync or CustomRPC.InspectorCommand or CustomRPC.ImitatorClick or CustomRPC.RetributionistClick or CustomRPC.StarspawnClick or CustomRPC.VentriloquistClick;
}
private static bool CheckRateLimit(PlayerControl __instance, RpcCalls rpcType)
@@ -631,17 +631,6 @@ internal static class RPCHandlerPatch
Main.AllPlayerSpeed[id] = speed;
break;
}
case CustomRPC.RequestSendMessage:
{
if (!AmongUsClient.Instance.AmHost) break;
string text = reader.ReadString();
byte sendTo = reader.ReadByte();
string title = reader.ReadString();
bool noSplit = reader.ReadBoolean();
Utils.SendMessage(text, sendTo, title, noSplit);
break;
}
case CustomRPC.NotificationPopper:
{
byte typeId = reader.ReadByte();
+55 -44
View File
@@ -825,6 +825,7 @@ public static class Utils
case CustomRoles.Eclipse:
case CustomRoles.Pyromaniac:
case CustomRoles.SerialKiller:
case CustomRoles.Quarry:
case CustomRoles.Spider:
case CustomRoles.SoulCollector:
case CustomRoles.Berserker:
@@ -960,6 +961,7 @@ public static class Utils
case CustomRoles.Cherokious:
case CustomRoles.Crewpostor:
case CustomRoles.Hypocrite:
case CustomRoles.Accumulator:
if (forRecompute && !p.IsDead) hasTasks = false;
if (p.IsDead) hasTasks = false;
break;
@@ -1831,14 +1833,7 @@ public static class Utils
if (!AmongUsClient.Instance.AmHost)
{
if (sendTo == PlayerControl.LocalPlayer.PlayerId && !multiple)
{
MessageWriter w = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestSendMessage, SendOption.Reliable, AmongUsClient.Instance.HostId);
w.Write(text);
w.Write(sendTo);
w.Write(title);
w.Write(noSplit);
AmongUsClient.Instance.FinishRpcImmediately(w);
}
SendLocally(PlayerControl.LocalPlayer);
return writer;
}
@@ -1860,21 +1855,7 @@ public static class Utils
if (sendTo != byte.MaxValue && receiver.AmOwner)
{
if (HudManager.InstanceExists)
{
string name = sender.Data.PlayerName;
sender.SetName(title);
HudManager.Instance.Chat.AddChat(sender, text);
sender.SetName(name);
}
try
{
string pureText = text.RemoveHtmlTags();
string pureTitle = title.RemoveHtmlTags();
Logger.Info($" Message: {pureText[..(pureText.Length <= 300 ? pureText.Length : 300)]} - To: {GetPlayerById(sendTo)?.GetRealName()} - Title: {pureTitle[..(pureTitle.Length <= 300 ? pureTitle.Length : 300)]}", "SendMessage");
}
catch { Logger.Info(" Message sent", "SendMessage"); }
SendLocally(sender);
if (addToHistory) ChatUpdatePatch.LastMessages.Add((text, sendTo, title, TimeStamp));
return writer;
@@ -2185,30 +2166,31 @@ public static class Utils
foreach (char c in text)
{
if (char.IsDigit(c))
if (char.IsDigit(c) && digitCount == 5)
{
digitCount++;
int lastNewline = sb.ToString().LastIndexOf('\n');
if (digitCount > 5)
if (lastNewline >= 0)
{
int lastNewline = sb.ToString().LastIndexOf('\n');
if (lastNewline >= 0)
{
result.Add(sb.ToString(0, lastNewline + 1));
sb.Remove(0, lastNewline + 1);
}
else
{
result.Add(sb.ToString());
sb.Clear();
}
digitCount = 1;
result.Add(sb.ToString(0, lastNewline + 1));
sb.Remove(0, lastNewline + 1);
}
else
{
result.Add(sb.ToString());
sb.Clear();
}
digitCount = 0;
foreach (char r in sb.ToString())
if (char.IsDigit(r))
digitCount++;
}
sb.Append(c);
if (char.IsDigit(c))
digitCount++;
}
if (sb.Length > 0)
@@ -2216,6 +2198,25 @@ public static class Utils
return result;
}
void SendLocally(PlayerControl sender)
{
if (HudManager.InstanceExists)
{
string name = sender.Data.PlayerName;
sender.SetName(title);
HudManager.Instance.Chat.AddChat(sender, text);
sender.SetName(name);
}
try
{
string pureText = text.RemoveHtmlTags();
string pureTitle = title.RemoveHtmlTags();
Logger.Info($" Message: {pureText[..(pureText.Length <= 300 ? pureText.Length : 300)]} - To: {PlayerControl.LocalPlayer.GetRealName()} - Title: {pureTitle[..(pureTitle.Length <= 300 ? pureTitle.Length : 300)]}", "SendMessage");
}
catch { Logger.Info(" Message sent", "SendMessage"); }
}
}
public static HashSet<byte> DirtyName = [];
@@ -3011,7 +3012,17 @@ public static class Utils
if (Options.CurrentGameMode != CustomGameMode.Standard) goto BeforeEnd;
if (guesserIsForMeeting || forMeeting || (seerRole == CustomRoles.Nemesis && !seer.IsAlive() && Options.NemesisCanKillNum.GetInt() >= 1))
targetPlayerName = $"{ColorString(GetRoleColor(seerRole), target.PlayerId.ToString())} {targetPlayerName}";
{
byte id = target.PlayerId;
if (Doppelganger.SwappedIDs.FindFirst(x => x.Item1 == id || x.Item2 == id, out var pair))
{
if (pair.Item1 == id) id = pair.Item2;
else if (pair.Item2 == id) id = pair.Item1;
}
targetPlayerName = $"{ColorString(GetRoleColor(seerRole), id.ToString())} {targetPlayerName}";
}
switch (seerRole)
{
@@ -3327,7 +3338,7 @@ public static class Utils
public static void SendGameData()
{
int messages = 0;
int packingLimit = 10;
int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit();
MessageWriter writer = MessageWriter.Get(SendOption.Reliable);
writer.StartMessage(5);
@@ -3361,7 +3372,7 @@ public static class Utils
public static void SendGameDataTo(int targetClientId)
{
int messages = 0;
int packingLimit = 10;
int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit();
MessageWriter writer = MessageWriter.Get(SendOption.Reliable);
writer.StartMessage(6);
@@ -3509,8 +3520,8 @@ public static class Utils
{
CustomRoles.Farmer => 2,
CustomRoles.Thanos => 5,
CustomRoles.PortalMaker => 5,
CustomRoles.Mole => Mole.CD.GetInt(),
CustomRoles.PortalMaker => PortalMaker.AbilityCooldown.GetInt(),
CustomRoles.Telecommunication => Telecommunication.VentCooldown.GetInt(),
CustomRoles.Tether => Tether.VentCooldown.GetInt(),
CustomRoles.Gardener => Gardener.AbilityCooldown.GetInt(),
+8 -1
View File
@@ -585,6 +585,12 @@ internal static class ChatCommands
private static void ConfirmAuthCommand(PlayerControl player, string commandKey, string text, string[] args)
{
if (!AmongUsClient.Instance.AmHost)
{
RequestCommandProcessingFromHost(text, commandKey);
return;
}
if (GameStates.CurrentServerType != GameStates.ServerType.Vanilla)
{
Utils.SendMessage("\n", player.PlayerId, GetString("ConfirmAuth.ErrorNotVanilla"));
@@ -1419,7 +1425,8 @@ internal static class ChatCommands
DraftResult = [];
byte[] allPlayerIds = Main.AllPlayerControls.Select(x => x.PlayerId).ToArray();
List<CustomRoles> allRoles = Enum.GetValues<CustomRoles>().Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !CustomHnS.AllHnSRoles.Contains(x) && !x.IsVanilla() && x is not CustomRoles.GM).Shuffle();
bool rollSpawnChance = Options.DraftAffectedByRoleSpawnChances.GetBool();
List<CustomRoles> allRoles = Enum.GetValues<CustomRoles>().Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !CustomHnS.AllHnSRoles.Contains(x) && !x.IsVanilla() && x is not CustomRoles.GM && (!rollSpawnChance || IRandom.Instance.Next(100) < x.GetMode())).Shuffle();
if (allRoles.Count < allPlayerIds.Length)
{
+1 -1
View File
@@ -203,7 +203,7 @@ public static class ChatManager
}
if (Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.SoloPVP or CustomGameMode.NaturalDisasters or CustomGameMode.Mingle or CustomGameMode.HideAndSeek && GameStates.InGame && !message.StartsWith('/'))
Main.AllAlivePlayerControls.NotifyPlayers(string.Format(Translator.GetString("FFAChatMessageNotify"), Main.GameModeColors.GetValueOrDefault(Options.CurrentGameMode, new(1,1,1)), player.PlayerId.ColoredPlayerName(), message));
Main.AllAlivePlayerControls.NotifyPlayers(string.Format(Utils.ColorString(Main.GameModeColors.GetValueOrDefault(Options.CurrentGameMode, new(1,1,1)), Translator.GetString("FFAChatMessageNotify")), player.PlayerId.ColoredPlayerName(), message));
}
public static void AddChatHistory(PlayerControl player, string message)
-10
View File
@@ -28,16 +28,6 @@ internal static class MakePublicPatch
}
}*/
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.GetMaxMessagePackingLimit))]
static class GetMaxMessagePackingLimitPatch
{
public static bool Prefix(ref int __result)
{
__result = 10;
return false;
}
}
[HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))]
// ReSharper disable once InconsistentNaming
internal static class MMOnlineManagerStartPatch
+9 -1
View File
@@ -956,6 +956,14 @@ internal static class MeetingHudStartPatch
Transform child = deathReasonTextMeeting.transform.FindChild("RoleTextMeeting");
if (child != null) Object.Destroy(child.gameObject);
byte id = pva.TargetPlayerId;
if (Doppelganger.SwappedIDs.FindFirst(x => x.Item1 == id || x.Item2 == id, out var pair))
{
if (pair.Item1 == id) id = pair.Item2;
else if (pair.Item2 == id) id = pair.Item1;
}
// Thanks BAU (By D1GQ) - are you happy now?
Transform playerLevel = pva.transform.Find("PlayerLevel");
Transform levelDisplay = Object.Instantiate(playerLevel, pva.transform);
@@ -967,7 +975,7 @@ internal static class MeetingHudStartPatch
Transform idNumber = levelDisplay.transform.Find("LevelNumber");
Object.Destroy(idLabel.GetComponent<TextTranslatorTMP>());
idLabel.GetComponent<TextMeshPro>().text = "ID";
idNumber.GetComponent<TextMeshPro>().text = pva.TargetPlayerId.ToString();
idNumber.GetComponent<TextMeshPro>().text = id.ToString();
idLabel.name = "IdLabel";
idNumber.name = "IdNumber";
}
+2 -1
View File
@@ -63,10 +63,11 @@ internal static class EndGamePatch
DateTime date = value.RealKiller.TimeStamp;
if (date == DateTime.MinValue) continue;
long secondsIn = new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeSeconds() - IntroCutsceneDestroyPatch.IntroDestroyTS;
byte killerId = value.GetRealKiller();
bool gmIsFm = Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.StopAndGo;
bool gmIsFmhh = gmIsFm || Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.HideAndSeek or CustomGameMode.Speedrun or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown;
sb.Append($"\n{date:T} {Main.AllPlayerNames[key]} ({(gmIsFmhh ? string.Empty : Utils.GetDisplayRoleName(key, true))}{(gmIsFm ? string.Empty : Utils.GetSubRolesText(key, summary: true))}) [{Utils.GetVitalText(key)}]");
sb.Append($"\n{secondsIn / 60:00}:{secondsIn % 60:00} {Main.AllPlayerNames[key]} ({(gmIsFmhh ? string.Empty : Utils.GetDisplayRoleName(key, true))}{(gmIsFm ? string.Empty : Utils.GetSubRolesText(key, summary: true))}) [{Utils.GetVitalText(key)}]");
if (killerId != byte.MaxValue && killerId != key) sb.Append($"\n\t⇐ {Main.AllPlayerNames[killerId]} ({(gmIsFmhh ? string.Empty : Utils.GetDisplayRoleName(killerId, true))}{(gmIsFm ? string.Empty : Utils.GetSubRolesText(killerId, summary: true))})");
}
+2 -2
View File
@@ -99,7 +99,7 @@ internal static class ExternalRpcPetPatch
LateTask.New(() => OnPetUse(pc), 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false);
}
public static void OnPetUse(PlayerControl pc)
private static void OnPetUse(PlayerControl pc)
{
if (pc == null ||
pc.inVent ||
@@ -191,7 +191,7 @@ internal static class ExternalRpcPetPatch
public static PlayerControl SelectKillButtonTarget(PlayerControl pc)
{
Vector2 pos = pc.Pos();
List<(PlayerControl pc, float distance)> players = Main.AllAlivePlayerControls.Without(pc).Select(x => (pc: x, distance: Vector2.Distance(pos, x.Pos()))).Where(x => x.distance < 2.5f).OrderBy(x => x.distance).ToList();
List<(PlayerControl pc, float distance)> players = Main.AllAlivePlayerControls.Without(pc).Select(x => (pc: x, distance: Vector2.Distance(pos, x.Pos()))).Where(x => x.distance < 3.5f).OrderBy(x => x.distance).ToList();
PlayerControl target = players.Count > 0 ? players[0].pc : null;
if (target != null && target.Is(CustomRoles.Detour))
+7
View File
@@ -264,6 +264,7 @@ internal static class CheckMurderPatch
if (ToiletMaster.OnAnyoneCheckMurderStart(killer, target)) return false;
if (Dad.OnAnyoneCheckMurderStart(target)) return false;
if (Quarry.OnAnyoneCheckMurderStart(killer, target)) return false;
Simon.RemoveTarget(killer, Simon.Instruction.Kill);
@@ -1069,6 +1070,12 @@ internal static class ReportDeadBodyPatch
return false;
}
if (!Quarry.OnAnyoneCheckReportDeadBody(__instance))
{
Notify("QuarryTarget");
return false;
}
if (!Librarian.OnAnyoneReport(__instance)) return false; // Player dies, no notify needed
if (!Trapster.OnAnyoneCheckReportDeadBody(__instance, target)) return false; // Player dies, no notify needed
+1 -1
View File
@@ -485,7 +485,7 @@ Click this badge to view our translation project:
### :star: [PL](https://github.com/Lotus-AU/LotusContinued) :
>
> - Ideas for some roles
> - Ideas for some roles (Alchemist, Chameleon, Escapist, Necromancer, Deathknight, Romantic and its variants, Vengeance)
> - Reference: Customize Main Menu
> - Reference: Auto Play Again
> - Reference: Some TabIcons
+1
View File
@@ -126,6 +126,7 @@ deltamalty#8755 | | Nggeereee | Toxic |
polarsail#2468 | | hatenggers | Toxic |
primesnow#4272 | 65b98a6de | HiverXM | Toxic | The n word
todayside#9634 | 4e44cffe6 | | Racist | Joined a lobby with racial slur in name
roundframe#4949 | 914830596 | PeaceMaker | Toxic | The n word
## Other
tenthrope#3476 | | Oldishwire | Other |
+1 -1
View File
@@ -10180,7 +10180,7 @@
"WinnerRoleText.Bubble": "Bubble Wins!",
"WinnerRoleText.Hookshot": "Hookshot Wins!",
"WinnerRoleText.Sprayer": "Sprayer Wins!",
"WinnerRoleText.SoulHunter": "Soul Hunter Wins!",
"WinnerRoleText.SoulHunter": "صائد الروح يفوز!",
"WinnerRoleText.Tiger": "Tiger Wins!",
"WinnerRoleText.Beehive": "فوز خلية النحل!",
"WinnerRoleText.Phantasm": "Phantasm Wins!",
+23 -2
View File
@@ -1199,6 +1199,7 @@
"CommsCamouflageLimitMaxTimesPerGame": "Maximum Times of Camouflaging Per Game",
"CommsCamouflageLimitMaxTimesPerRound": "Maximum Times of Camouflaging Per Round",
"CommsCamouflageSetSameSpeed": "All Players Have the Same Speed During Camouflage",
"CommsCamouflagePreventRound1": "Don't Camouflage Round 1",
"DisableReportWhenCC": "Disable body reporting while camouflaged",
"EnableDebugMode": "Enable Debug Mode",
"ChangeNameToRoleInfo": "Show Role Info to Unmodded Clients Round 1",
@@ -3024,6 +3025,7 @@
"DraftTitle": "<#00a5ff>➡</color> <#00ffa5>Drafting</color> <#00a5ff>⬅</color>",
"DraftChosen": "You have chosen the <b>{0}</b> role for the next game.\\n<size=70%>You can still change your choice by typing /draft [index] again.</size>",
"DraftMaxRolesPerPlayer": "Max Roles For Each Player To Choose From When Drafting",
"DraftAffectedByRoleSpawnChances": "Take Role Spawn Chances Into Account When Picking Draft Choices",
"EveryoneDrafted": "<#00ffa5>✓</color> <#00ff00>Everyone has drafted their role!</color>",
"DeathCommand": "<#ffffff>You were killed by </color>{0}<#ffffff>\\nTheir role is </color>{1}",
"DeathCommandFail": "<#ff0000>The game could not determine your real killer.</color>\\nYour death reason is <#00ffa5>{0}</color>.",
@@ -4455,6 +4457,7 @@
"PortalMaker": "Portal Maker",
"PortalMakerInfo": "Make Portals & Teleport Between Them",
"PortalMakerInfoLong": "(Crewmates):\\nAs the Portal Maker, you can place 2 permanent portals by petting. After placing both portals, if anyone goes near one of them, they will be teleported to the other portal. Portal usage has a 5 second cooldown.",
"PortalMaker.PetToRemovePortals": "Can Pet To Remove Both Portals After Placing Them",
"RiftMaker": "Rift Maker",
"RiftMakerInfo": "Teleport between 2 places",
"RiftMakerInfoLong": "(Impostors):\\nAs the Rift Maker, you may shapeshift twice/pet twice to mark two points on the ship. After you've marked both places, whenever you go near a marked place, you will automatically teleport to the other mark point.\\nVenting discards both marked places.\\n\\nYou cannot vent normally.",
@@ -4973,7 +4976,7 @@
"FFA_ShieldIsOneTimeUse": "Shields break after 1 kill attempt",
"FFA_ShieldBroken": "<color=#ffff00>Someone tried to kill you, your shield is now broken!</color>",
"FFA_ChatDuringGame": "Chat During Game",
"FFAChatMessageNotify": "<#ffffff><#00ffff>[Chat]</color> {0}: {1}",
"FFAChatMessageNotify": "[Chat] {0}: <#ffffff>{1}",
"Killer": "FREE FOR ALL",
"KillerInfo": "KILL EVERYONE TO WIN",
"KillerInfoLong": "KILL EVERYONE TO WIN",
@@ -9247,6 +9250,15 @@
"Telekinetic.Mode.Shield": "Shield",
"Telekinetic.Mode.Speed": "Speed",
"Telekinetic.Mode.Doors": "Doors",
"Quarry": "Quarry",
"QuarryInfo": "Hide From Players",
"QuarryInfoLong": "(Neutrals):\\nAs the Quarry, you may shapeshift to select your target and swap places with them, they then have to kill you in a specified time or they die. If they find and kill you, they live. You can also kill normally using the kill button.\\nKill everyone to win!",
"Quarry.TargetHasArrowToQuarry": "Target has an arrow to the <#c1fb2b>Quarry</color>",
"Quarry.CanVentDuringSeekTime": "Can Vent During Seek Time",
"Quarry.TargetDiesOnMeetingCall": "Target Dies on Meeting Call",
"Quarry.SeekTime": "Seek Time",
"Quarry.TargetSeekBeginNotify": "<#ff0000>You were swapped with the {0}! They have a black name!",
"Quarry.TimeLeftSuffix": "<#ffff00>You have <#ffffff>{0}s</color> to kill the {1} <#ffa500>or you die!",
"Nonplus": "Nonplus",
"NonplusInfo": "Blind Others",
"NonplusInfoLong": "(Neutrals):\\nAs the Nonplus, you can blind others by venting or petting. Blinded players can't see anything for a set amount of time.\\nKill everyone to win.",
@@ -9516,6 +9528,14 @@
"ImmuneToAttacksWhenTasksDone": "Immune to attacks on task completion",
"TwisterCooldown": "Twist Cooldown",
"TwisterButtonText": "Twist",
"Accumulator": "Accumulator",
"AccumulatorInfo": "Do Tasks For Low Kill Cooldown",
"AccumulatorInfoLong": "(Neutrals):\\nAs the Accumulator, you start with a crewmate basis, and you can do tasks. Each task you complete lowers your kill cooldown (that you'll eventually have). You can pet or finish all tasks to turn into a killing neutral with the kill cooldown you accumulated.\\nKill everyone to win!",
"Accumulator.KillCooldownDecrementWithEachTask": "Kill Cooldown Decrement With Each Task",
"Accumulator.CanVentBeforeKilling": "Can Vent Before Killing",
"Accumulator.CanVentAfterKilling": "Can Vent After Killing",
"Accumulator.ImpostorVisionBeforeKilling": "Has Impostor Vision Before Killing",
"Accumulator.ImpostorVisionAfterKilling": "Has Impostor Vision After Killing",
"MainMenu.SettingsButton": "Settings",
"MainMenu.InventoryButton": "Inventory",
"MainMenu.CreditsButton": "Credits",
@@ -10028,9 +10048,10 @@
"Goddess.Suffix": "<{1}>Stone Gazing</color> <#888888>-</color> <b>{0}</b>",
"Augur": "Augur",
"AugurInfo": "Guess roles",
"AugurInfoLong": "(Coven):\\nYou can guess players' roles during meetings. You cannot gain the Necronomicon, similar to a Reaper.",
"AugurInfoLong": "(Coven):\\nYou can guess players' roles during meetings. If you misguess, you don't die, but you can't guess again until the next meeting. You cannot gain the Necronomicon, similar to a Reaper.",
"Augur.MaxGuessesPerMeeting": "Max number of guesses per meeting",
"Augur.MaxGuessesPerGame": "Max number of guesses per game",
"Augur.IncorrectGuess": "<#ff0000>Incorrect!</color> You can't guess in this meeting anymore.",
"Dreamweaver": "Dreamweaver",
"DreamweaverInfo": "Drive players insane with weaving",
"DreamweaverInfoLong": "(Coven):\\nWeave a player; woven players will become insane after the next meeting. Once insane, when they go near someone, they will immediately use their ability if their cooldown is up. Upon your death, all insane players will be freed. When enhanced by the Necronomicon, insane players will suicide after making contact with another player.",
+22 -2
View File
@@ -1,4 +1,7 @@
using System.Collections.Generic;
using EHR.Modules;
using Hazel;
namespace EHR.Roles;
public class Deadlined : IAddon
@@ -21,12 +24,14 @@ public class Deadlined : IAddon
{
DidTask.Add(pc.PlayerId);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.SendRPC(CustomRPC.Deadlined, 1, pc.PlayerId);
}
public static void AfterMeetingTasks()
{
DidTask = [];
MeetingEndTS = Utils.TimeStamp;
Utils.SendRPC(CustomRPC.Deadlined, 2);
foreach (PlayerControl pc in Main.AllPlayerControls)
{
@@ -40,13 +45,28 @@ public class Deadlined : IAddon
{
if (MeetingStates.FirstMeeting) return;
if (MeetingEndTS + InactiveTime.GetInt() > Utils.TimeStamp) return;
if (MeetingEndTS + InactiveTime.GetInt() >= Utils.TimeStamp) return;
foreach (PlayerControl pc in Main.AllAlivePlayerControls)
{
if (!pc.Is(CustomRoles.Deadlined)) continue;
if (!DidTask.Contains(pc.PlayerId)) pc.Suicide();
if (!DidTask.Contains(pc.PlayerId))
pc.Suicide();
}
}
public static void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
DidTask.Add(reader.ReadByte());
break;
case 2:
DidTask = [];
MeetingEndTS = Utils.TimeStamp - 1;
break;
}
}
+13
View File
@@ -16,6 +16,8 @@ public class Augur : CovenBase
public override bool IsEnable => On;
public bool CanGuess;
public override void SetupCustomOption()
{
StartSetup(650070)
@@ -34,6 +36,7 @@ public class Augur : CovenBase
public override void Add(byte playerId)
{
On = true;
CanGuess = true;
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
@@ -44,4 +47,14 @@ public class Augur : CovenBase
AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat();
}
}
public override void OnReportDeadBody()
{
CanGuess = true;
}
public override void AfterMeetingTasks()
{
CanGuess = true;
}
}
@@ -16,9 +16,14 @@ public class PortalMaker : RoleBase
private List<Vector2> Marks;
private Dictionary<byte, long> LastTP;
public static OptionItem AbilityCooldown;
private static OptionItem PetToRemovePortals;
public override void SetupCustomOption()
{
StartSetup(652800);
StartSetup(652800)
.AutoSetupOption(ref AbilityCooldown, 5, new IntegerValueRule(1, 120, 1), OptionFormat.Seconds)
.AutoSetupOption(ref PetToRemovePortals, true);
}
public override void Init()
@@ -36,7 +41,14 @@ public class PortalMaker : RoleBase
public override void OnPet(PlayerControl pc)
{
if (Marks.Count == 2) return;
if (Marks.Count == 2)
{
if (!PetToRemovePortals.GetBool()) return;
Marks.Clear();
CustomNetObject.AllObjects.OfType<Portal>().Do(x => x.Despawn());
pc.Notify(Translator.GetString("MarksCleared"));
return;
}
Vector2 pos = pc.Pos();
if (Marks.Count == 1 && Vector2.Distance(Marks[0], pos) < 4f) return;
+3 -3
View File
@@ -127,6 +127,7 @@ public class Alchemist : RoleBase
public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount)
{
PotionID = (byte)IRandom.Instance.Next(1, 8);
SendRPCData();
switch (PotionID)
{
@@ -233,10 +234,9 @@ public class Alchemist : RoleBase
player.Notify(GetString("AlchemistNoPotion"));
break;
}
am.SendRPCData();
am.PotionID = 10;
am.SendRPCData();
}
private void SendRPC()
+6 -11
View File
@@ -57,35 +57,30 @@ internal class Beacon : RoleBase
public override void OnCheckPlayerPosition(PlayerControl pc)
{
if (!GameStates.IsInTask || pc == null) return;
long now = Utils.TimeStamp;
if (LastChange.TryGetValue(pc.PlayerId, out long ts) && ts == now) return;
Vector2 pos = pc.Pos();
float radius = Radius.GetFloat();
bool beaconNearby = Beacons.Any(x => Vector2.Distance(x.Pos(), pos) <= radius);
bool affectedPlayer = AffectedPlayers.Contains(pc.PlayerId);
bool lightsOff = Utils.IsActive(SystemTypes.Electrical);
switch (affectedPlayer)
switch (affectedPlayer: AffectedPlayers.Contains(pc.PlayerId), beaconNearby: Utils.IsActive(SystemTypes.Electrical) && Beacons.Any(x => Vector2.Distance(x.Pos(), pos) <= radius))
{
case true when !beaconNearby:
case (affectedPlayer: true, beaconNearby: false):
{
AffectedPlayers.Remove(pc.PlayerId);
if (lightsOff) pc.MarkDirtySettings();
pc.MarkDirtySettings();
LastChange[pc.PlayerId] = now;
break;
}
case false when beaconNearby:
case (affectedPlayer: false, beaconNearby: true):
{
AffectedPlayers.Add(pc.PlayerId);
if (lightsOff) pc.MarkDirtySettings();
pc.MarkDirtySettings();
LastChange[pc.PlayerId] = now;
if (pc.AmOwner && lightsOff)
if (pc.AmOwner)
Achievements.Type.ALightInTheShadows.CompleteAfterGameEnd();
break;
@@ -85,10 +85,16 @@ public class Mastermind : RoleBase
});
}
private long LastUpdate;
public override void OnFixedUpdate(PlayerControl _)
{
if (!IsEnable || GameStates.IsMeeting || (ManipulatedPlayers.Count == 0 && ManipulateDelays.Count == 0)) return;
long now = TimeStamp;
if (now == LastUpdate) return;
LastUpdate = now;
foreach (KeyValuePair<byte, long> x in ManipulateDelays)
{
PlayerControl pc = GetPlayerById(x.Key);
@@ -99,10 +105,10 @@ public class Mastermind : RoleBase
continue;
}
if (x.Value + Delay.GetInt() < TimeStamp)
if (x.Value + Delay.GetInt() < now)
{
ManipulateDelays.Remove(x.Key);
ManipulatedPlayers.TryAdd(x.Key, TimeStamp);
ManipulatedPlayers.TryAdd(x.Key, now);
if (pc.HasKillButton()) TempKCDs.TryAdd(pc.PlayerId, Main.KillTimers[pc.PlayerId]);
else pc.RpcChangeRoleBasis(CustomRoles.SerialKiller);
@@ -123,7 +129,7 @@ public class Mastermind : RoleBase
continue;
}
if (x.Value + TimeLimit.GetInt() < TimeStamp)
if (x.Value + TimeLimit.GetInt() < now)
{
ManipulatedPlayers.Remove(x.Key);
if (!TempKCDs.Remove(x.Key)) player.RpcChangeRoleBasis(player.GetCustomRole());
@@ -134,7 +140,7 @@ public class Mastermind : RoleBase
Achievements.Type.OutOfTime.Complete();
}
long time = TimeLimit.GetInt() - (TimeStamp - x.Value);
long time = TimeLimit.GetInt() - (now - x.Value);
player.Notify(string.Format(GetString("ManipulateNotify"), time), 3f, true);
}
@@ -184,7 +184,7 @@ public class Swooper : RoleBase
{
long cooldown = lastTime + (long)Cooldown - now;
if ((int)cooldown != CD && !(UsedRole != CustomRoles.Chameleon && UsePhantomBasis.GetBool() && (UsedRole != CustomRoles.Wraith || UsePhantomBasisForNKs.GetBool())))
if ((int)cooldown != CD && !(UsePhantomBasis.GetBool() && (UsedRole != CustomRoles.Wraith || UsePhantomBasisForNKs.GetBool())))
player.Notify(string.Format(GetString("CDPT"), cooldown + 1), 3f, true);
CD = (int)cooldown;
@@ -212,7 +212,7 @@ public class Swooper : RoleBase
case < 0:
lastTime = now;
if (UsedRole == CustomRoles.Chameleon)
if (UsedRole == CustomRoles.Chameleon && !UsePets.GetBool())
{
int ventId = ventedId == -10 ? Main.LastEnteredVent[player.PlayerId].Id : ventedId;
Main.AllPlayerControls.Without(player).Do(x => player.MyPhysics.RpcExitVentDesync(ventId, x));
@@ -0,0 +1,118 @@
using System;
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
namespace EHR.Roles;
public class Accumulator : RoleBase
{
public static bool On;
private static OptionItem StartingKillCooldown;
private static OptionItem KillCooldownDecrementWithEachTask;
public static OptionItem CanVentBeforeKilling;
private static OptionItem VentCooldown;
private static OptionItem MaxInVentTime;
private static OptionItem CanVentAfterKilling;
private static OptionItem ImpostorVisionBeforeKilling;
private static OptionItem ImpostorVisionAfterKilling;
public override bool IsEnable => On;
private bool Killing;
private float KCD;
private byte AccumulatorId;
public override void SetupCustomOption()
{
StartSetup(657700)
.AutoSetupOption(ref StartingKillCooldown, 30f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref KillCooldownDecrementWithEachTask, 3f, new FloatValueRule(0f, 30f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref CanVentBeforeKilling, false)
.AutoSetupOption(ref VentCooldown, 0f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVentBeforeKilling)
.AutoSetupOption(ref MaxInVentTime, 0f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVentBeforeKilling)
.AutoSetupOption(ref CanVentAfterKilling, true)
.AutoSetupOption(ref ImpostorVisionBeforeKilling, false)
.AutoSetupOption(ref ImpostorVisionAfterKilling, true)
.CreateOverrideTasksData();
}
public override void Init()
{
On = false;
}
public override void Add(byte playerId)
{
On = true;
Killing = false;
KCD = StartingKillCooldown.GetFloat();
AccumulatorId = playerId;
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = KCD;
}
public override bool CanUseKillButton(PlayerControl pc)
{
return Killing;
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
opt.SetVision(Killing ? ImpostorVisionAfterKilling.GetBool() : ImpostorVisionBeforeKilling.GetBool());
if (!Killing && CanVentBeforeKilling.GetBool())
{
AURoleOptions.EngineerCooldown = VentCooldown.GetFloat();
AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat();
}
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
{
return Killing && CanVentAfterKilling.GetBool();
}
public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount)
{
KCD -= KillCooldownDecrementWithEachTask.GetFloat();
Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, KCD, Killing);
if (completedTaskCount + 1 >= totalTaskCount)
OnPet(pc);
}
public override void OnPet(PlayerControl pc)
{
if (Killing) return;
Killing = true;
pc.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId, setRoleMap: true);
LateTask.New(() => pc.SetKillCooldown(KCD), 0.2f);
Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, KCD, Killing);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
public override void AfterMeetingTasks()
{
if (!Killing) return;
PlayerControl pc = AccumulatorId.GetPlayer();
if (pc == null || !pc.IsAlive()) return;
pc.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId, setRoleMap: true);
}
public void ReceiveRPC(MessageReader reader)
{
KCD = reader.ReadSingle();
Killing = reader.ReadBoolean();
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.PlayerId != AccumulatorId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || Killing) return string.Empty;
return string.Format(Translator.GetString("KCD"), Math.Round(Math.Max(0, KCD), 1));
}
}
@@ -23,6 +23,7 @@ public class Doppelganger : RoleBase
public static int LocalPlayerChangeSkinTimes;
public static HashSet<(byte, byte)> SwappedIDs = [];
public static Dictionary<byte, string> DoppelVictim = [];
public static Dictionary<byte, NetworkedPlayerInfo.PlayerOutfit> DoppelPresentSkin = [];
private static Dictionary<byte, int> TotalSteals = [];
@@ -68,6 +69,7 @@ public class Doppelganger : RoleBase
public override void Init()
{
PlayerIdList = [];
SwappedIDs = [];
DoppelVictim = [];
TotalSteals = [];
DoppelPresentSkin = [];
@@ -255,6 +257,9 @@ public class Doppelganger : RoleBase
DoppelVictim[target.PlayerId] = tname;
SwappedIDs.RemoveWhere(x => x.Item1 == killer.PlayerId || x.Item2 == killer.PlayerId);
SwappedIDs.Add((killer.PlayerId, target.PlayerId));
RpcChangeSkin(target, killerSkin);
Logger.Info("Changed target skin", "Doppelganger");
+247
View File
@@ -0,0 +1,247 @@
using System.Collections.Generic;
using System.Diagnostics;
using AmongUs.GameOptions;
using EHR.Modules;
using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
public class Quarry : RoleBase
{
public static bool On;
private static List<Quarry> Instances = [];
private static OptionItem TargetHasArrowToQuarry;
private static OptionItem CanVentDuringSeekTime;
private static OptionItem TargetDiesOnMeetingCall;
private static OptionItem SeekTime;
private static OptionItem ShapeshiftCooldown;
private static OptionItem KillCooldown;
private static OptionItem CanVent;
private static OptionItem ImpostorVision;
private static OptionItem AbilityUseLimit;
private static OptionItem AbilityUseGainWithEachKill;
private static OptionItem CooldownsResetEachOther;
public override bool IsEnable => On;
private byte QuarryId;
public byte TargetId;
private Stopwatch SeekTimer;
private long LastUpdate;
public override void SetupCustomOption()
{
StartSetup(657800)
.AutoSetupOption(ref TargetHasArrowToQuarry, true)
.AutoSetupOption(ref CanVentDuringSeekTime, false)
.AutoSetupOption(ref TargetDiesOnMeetingCall, false)
.AutoSetupOption(ref SeekTime, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds)
.AutoSetupOption(ref ShapeshiftCooldown, 15f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref CanVent, true)
.AutoSetupOption(ref ImpostorVision, true)
.AutoSetupOption(ref AbilityUseLimit, 2f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachKill, 1f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times)
.AutoSetupOption(ref CooldownsResetEachOther, true);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
QuarryId = playerId;
TargetId = byte.MaxValue;
SeekTimer = new();
LastUpdate = 0;
Instances.Add(this);
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
}
public override void Remove(byte playerId)
{
Instances.Remove(this);
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
opt.SetVision(ImpostorVision.GetBool());
AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
{
return CanVent.GetBool() && (TargetId == byte.MaxValue || CanVentDuringSeekTime.GetBool());
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
return target.PlayerId != TargetId;
}
public override void OnMurder(PlayerControl killer, PlayerControl target)
{
if (CooldownsResetEachOther.GetBool())
killer.RpcResetAbilityCooldown();
}
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
if (!shapeshifting) return true;
if (shapeshifter.GetAbilityUseLimit() < 1) return false;
shapeshifter.RpcRemoveAbilityUse();
Vector2 pos = shapeshifter.Pos();
shapeshifter.TP(target);
target.TP(pos);
TargetId = target.PlayerId;
SeekTimer = Stopwatch.StartNew();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 1, TargetId);
Main.Instance.StartCoroutine(ContinuouslyResetAbilityCooldown());
if (TargetHasArrowToQuarry.GetBool()) TargetArrow.Add(target.PlayerId, shapeshifter.PlayerId);
var sender = CustomRpcSender.Create("QuarryTarget", SendOption.Reliable);
var hasValue = false;
hasValue |= sender.Notify(target, string.Format(Translator.GetString("Quarry.TargetSeekBeginNotify"), CustomRoles.Quarry.ToColoredString()));
hasValue |= sender.RpcSetRole(target, RoleTypes.Impostor, target.OwnerId);
hasValue |= sender.RpcSetRole(shapeshifter, RoleTypes.Crewmate, target.OwnerId);
hasValue |= sender.NotifyRolesSpecific(target, shapeshifter, out sender, out bool cleared);
if (cleared) hasValue = false;
sender.SendMessage(dispose: !hasValue);
if (CooldownsResetEachOther.GetBool())
shapeshifter.SetKillCooldown();
return false;
System.Collections.IEnumerator ContinuouslyResetAbilityCooldown()
{
while (TargetId != byte.MaxValue)
{
int interval = ShapeshiftCooldown.GetInt() - 2;
Stopwatch stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed.Seconds < interval) yield return null;
if (TargetId == byte.MaxValue) yield break;
shapeshifter.RpcResetAbilityCooldown();
}
}
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (!Main.IntroDestroyed || !GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || !pc.IsAlive() || TargetId == byte.MaxValue) return;
long now = Utils.TimeStamp;
if (now == LastUpdate) return;
LastUpdate = now;
PlayerControl target = TargetId.GetPlayer();
if (target == null || !target.IsAlive())
{
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
return;
}
if (SeekTimer.GetRemainingTime(SeekTime.GetInt()) <= 0)
{
target.Suicide();
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
}
else
{
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
}
}
public static bool OnAnyoneCheckMurderStart(PlayerControl killer, PlayerControl target)
{
foreach (Quarry instance in Instances)
{
if (instance.TargetId == killer.PlayerId && instance.QuarryId == target.PlayerId)
{
killer.Kill(target);
killer.RpcSetRoleDesync(killer.GetRoleTypes(), killer.OwnerId);
instance.TargetId = byte.MaxValue;
instance.SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, instance.QuarryId, 2);
return true;
}
}
return false;
}
public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter)
{
return !Instances.Exists(x => x.TargetId == reporter.PlayerId);
}
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return pc.PlayerId != TargetId;
}
public override void OnReportDeadBody()
{
if (TargetId == byte.MaxValue) return;
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
PlayerControl target = TargetId.GetPlayer();
if (target == null || !target.IsAlive()) return;
if (TargetDiesOnMeetingCall.GetBool())
target.Suicide();
else
{
PlayerControl pc = QuarryId.GetPlayer();
if (pc == null || !pc.IsAlive()) return;
pc.RpcIncreaseAbilityUseLimitBy(1);
}
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
TargetId = reader.ReadByte();
SeekTimer = Stopwatch.StartNew();
break;
case 2:
TargetId = byte.MaxValue;
SeekTimer.Reset();
break;
}
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if ((seer.PlayerId != QuarryId && seer.PlayerId != TargetId) || seer.PlayerId != target.PlayerId || meeting || hud || TargetId == byte.MaxValue) return string.Empty;
string timer = string.Format(Translator.GetString("Quarry.TimeLeftSuffix"), SeekTimer.GetRemainingTime(SeekTime.GetInt()) - 1, CustomRoles.Quarry.ToColoredString());
return seer.PlayerId == TargetId ? $"{TargetArrow.GetAllArrows(TargetId)}\n{timer}" : timer;
}
}
+6 -4
View File
@@ -53,7 +53,7 @@ public class Starspawn : RoleBase
public override bool CanUseKillButton(PlayerControl pc)
{
return pc.IsAlive();
return pc.GetAbilityUseLimit() >= 1;
}
public override void SetKillCooldown(byte id)
@@ -68,7 +68,9 @@ public class Starspawn : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
IsolatedPlayers.Add(target.PlayerId);
if (!IsolatedPlayers.Add(target.PlayerId)) return false;
killer.RpcRemoveAbilityUse();
killer.SetKillCooldown(AbilityCooldown.GetFloat());
Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target);
Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, target.PlayerId);
return false;
@@ -92,7 +94,7 @@ public class Starspawn : RoleBase
if (AmongUsClient.Instance.AmHost)
{
var command = $"/daybreak";
const string command = "/daybreak";
ChatCommands.DayBreakCommand(PlayerControl.LocalPlayer, "Command.Daybreak", command, command.Split(' '));
}
else
@@ -105,7 +107,7 @@ public class Starspawn : RoleBase
LateTask.New(() => starspawnButton.SetActive(true), 1f, "StarspawnButton");
}
public static void CreateStarspawnButton(MeetingHud __instance)
private static void CreateStarspawnButton(MeetingHud __instance)
{
if (GameObject.Find("StarspawnButton") != null) Object.Destroy(GameObject.Find("StarspawnButton").gameObject);
if (PlayerControl.LocalPlayer == null || !PlayerControl.LocalPlayer.IsAlive()) return;