Files
EndlessHostRoles/Patches/PlayerControlPatch.cs
T
2024-03-04 18:43:21 +01:00

2015 lines
87 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AmongUs.GameOptions;
using HarmonyLib;
using Hazel;
using TMPro;
using TOHE.Modules;
using TOHE.Patches;
using TOHE.Roles.AddOns.Common;
using TOHE.Roles.AddOns.Crewmate;
using TOHE.Roles.AddOns.Impostor;
using TOHE.Roles.Crewmate;
using TOHE.Roles.Impostor;
using TOHE.Roles.Neutral;
using UnityEngine;
using static TOHE.Translator;
using static TOHE.Utils;
using Object = UnityEngine.Object;
namespace TOHE;
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckProtect))]
class CheckProtectPatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
if (!AmongUsClient.Instance.AmHost) return false;
Logger.Info("CheckProtect: " + __instance.GetNameWithRole().RemoveHtmlTags() + " => " + target.GetNameWithRole().RemoveHtmlTags(), "CheckProtect");
if (__instance.Is(CustomRoles.EvilSpirit))
{
if (target.Is(CustomRoles.Spiritcaller))
{
Spiritcaller.ProtectSpiritcaller();
}
else
{
Spiritcaller.HauntPlayer(target);
}
__instance.RpcResetAbilityCooldown();
return true;
}
if (__instance.Is(CustomRoles.Sheriff))
{
if (__instance.Data.IsDead)
{
Logger.Info("Blocked", "CheckProtect");
return false;
}
}
return true;
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckMurder))] // Modded
class CmdCheckMurderPatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
if (!AmongUsClient.Instance.AmHost) return true;
return CheckMurderPatch.Prefix(__instance, target);
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckMurder))] // Vanilla
class CheckMurderPatch
{
public static Dictionary<byte, float> TimeSinceLastKill = [];
public static void Update()
{
for (byte i = 0; i < 15; i++)
{
if (TimeSinceLastKill.ContainsKey(i))
{
TimeSinceLastKill[i] += Time.deltaTime;
if (15f < TimeSinceLastKill[i]) TimeSinceLastKill.Remove(i);
}
}
}
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
if (!AmongUsClient.Instance.AmHost) return false;
var killer = __instance; // alternative variable
Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "CheckMurder");
if (killer.Data.IsDead)
{
Logger.Info($"Killer {killer.GetNameWithRole().RemoveHtmlTags()} is dead, kill canceled", "CheckMurder");
return false;
}
if (target.Is(CustomRoles.Detour))
{
var tempTarget = target;
target = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId && x.PlayerId != killer.PlayerId).MinBy(x => Vector2.Distance(x.Pos(), target.Pos()));
Logger.Info($"Target was {tempTarget.GetNameWithRole()}, new target is {target.GetNameWithRole()}", "Detour");
}
if (target.Data == null
|| target.inVent
|| target.inMovingPlat
|| target.MyPhysics.Animations.IsPlayingEnterVentAnimation()
|| target.MyPhysics.Animations.IsPlayingAnyLadderAnimation()
|| target.onLadder)
{
Logger.Info("The target is in a state where they cannot be killed, kill canceled.", "CheckMurder");
return false;
}
if (target.Data.IsDead)
{
Logger.Info("Target is already dead, kill canceled", "CheckMurder");
return false;
}
if (MeetingHud.Instance != null)
{
Logger.Info("Kill during meeting, canceled", "CheckMurder");
return false;
}
var divice = Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato ? 3000f : 2000f;
float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); // The value of AmongUsClient.Instance.Ping is in milliseconds (ms), so ÷1000
// No value is stored in TimeSinceLastKill || Stored time is greater than or equal to minTime => Allow kill
// ↓ If not allowed
if (TimeSinceLastKill.TryGetValue(killer.PlayerId, out var time) && time < minTime)
{
Logger.Info("Last kill was too shortly before, canceled", "CheckMurder");
return false;
}
TimeSinceLastKill[killer.PlayerId] = 0f;
if (target.Is(CustomRoles.Diseased))
{
if (!Main.KilledDiseased.TryAdd(killer.PlayerId, 1))
{
// Key already exists, update the value
Main.KilledDiseased[killer.PlayerId] += 1;
}
// Key doesn't exist, add the key-value pair
}
if (target.Is(CustomRoles.Antidote))
{
if (!Main.KilledAntidote.TryAdd(killer.PlayerId, 1))
{
// Key already exists, update the value
Main.KilledAntidote[killer.PlayerId] += 1; // Main.AllPlayerKillCooldown.TryGetValue(killer.PlayerId, out float kcd) ? (kcd - Options.AntidoteCDOpt.GetFloat() > 0 ? kcd - Options.AntidoteCDOpt.GetFloat() : 0f) : 0f;
}
// Key doesn't exist, add the key-value pair
// Main.AllPlayerKillCooldown.TryGetValue(killer.PlayerId, out float kcd) ? (kcd - Options.AntidoteCDOpt.GetFloat() > 0 ? kcd - Options.AntidoteCDOpt.GetFloat() : 0f) : 0f);
}
killer.ResetKillCooldown();
if (killer.PlayerId != target.PlayerId && !killer.CanUseKillButton())
{
Logger.Info(killer.GetNameWithRole().RemoveHtmlTags() + " cannot use their kill button, the kill was blocked", "CheckMurder");
return false;
}
switch (Options.CurrentGameMode)
{
case CustomGameMode.SoloKombat:
SoloKombatManager.OnPlayerAttack(killer, target);
return false;
case CustomGameMode.FFA:
FFAManager.OnPlayerAttack(killer, target);
return true;
case CustomGameMode.MoveAndStop:
case CustomGameMode.HotPotato:
return false;
}
if (Mastermind.ManipulatedPlayers.ContainsKey(killer.PlayerId))
{
return Mastermind.ForceKillForManipulatedPlayer(killer, target);
}
if (target.Is(CustomRoles.Spy) && !Spy.OnKillAttempt(killer, target)) return false;
if (Penguin.IsVictim(killer)) return false;
Sniper.TryGetSniper(target.PlayerId, ref killer);
if (killer != __instance) Logger.Info($"Real Killer: {killer.GetNameWithRole().RemoveHtmlTags()}", "CheckMurder");
if (Pelican.IsEaten(target.PlayerId))
return false;
if (Glitch.hackedIdList.ContainsKey(killer.PlayerId))
{
killer.Notify(string.Format(GetString("HackedByGlitch"), "Kill"));
return false;
}
if (Pursuer.OnClientMurder(killer)) return false;
if (killer.PlayerId != target.PlayerId && !Main.PlayerStates[killer.PlayerId].Role.OnCheckMurder(killer, target)) return false;
if (!killer.RpcCheckAndMurder(target, true))
return false;
if (killer.Is(CustomRoles.Unlucky))
{
var Ue = IRandom.Instance;
if (Ue.Next(0, 100) < Options.UnluckyKillSuicideChance.GetInt())
{
killer.Suicide();
return false;
}
}
if (killer.Is(CustomRoles.Mare))
{
killer.ResetKillCooldown();
}
if (!DoubleTrigger.FirstTriggerTimer.ContainsKey(killer.PlayerId) && killer.Is(CustomRoles.Swift) && !target.Is(CustomRoles.Pestilence))
{
if (killer.RpcCheckAndMurder(target, true))
{
target.Suicide(PlayerState.DeathReason.Kill, killer);
killer.SetKillCooldown();
}
RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound);
return false;
}
if (killer.Is(CustomRoles.Magnet) && !target.Is(CustomRoles.Pestilence))
{
target.TP(killer);
_ = new LateTask(() => { killer.RpcCheckAndMurder(target); }, 0.1f, log: false);
return false;
}
//==Kill processing==
__instance.Kill(target);
//===================
return false;
}
public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, bool check = false)
{
if (!AmongUsClient.Instance.AmHost) return false;
if (target == null) target = killer;
//Jackal can kill Sidekick
if (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool())
return false;
//Sidekick can kill Jackal
if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool())
return false;
if (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Recruit) && !Options.JackalCanKillSidekick.GetBool())
return false;
//Sidekick can kill Jackal
if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool())
return false;
if (killer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Madmate) && !Options.ImpCanKillMadmate.GetBool())
return false;
// Romantic partner is protected
if (Romantic.PartnerId == target.PlayerId && Romantic.IsPartnerProtected) return false;
if (Medic.OnAnyoneCheckMurder(killer, target))
return false;
if (Mathematician.State.ProtectedPlayerId == target.PlayerId) return false;
if (Randomizer.IsShielded(target)) return false;
if (Penguin.IsVictim(killer)) return false;
if (killer.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) return false;
if (SoulHunter.IsSoulHunterTarget(killer.PlayerId) && target.Is(CustomRoles.SoulHunter))
{
killer.Notify(GetString("SoulHunterTargetNotifyNoKill"));
_ = new LateTask(() =>
{
if (SoulHunter.IsSoulHunterTarget(killer.PlayerId)) killer.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter.GetSoulHunter(killer.PlayerId).SoulHunter_.GetRealName()), 300f);
}, 4f, log: false);
return false;
}
if (Glitch.hackedIdList.ContainsKey(killer.PlayerId))
{
killer.Notify(string.Format(GetString("HackedByGlitch"), "Kill"));
return false;
}
// Traitor can't kill Impostors but Impostors can kill it
if (killer.Is(CustomRoles.Traitor) && target.Is(CustomRoleTypes.Impostor))
return false;
//禁止叛徒刀内鬼
if (killer.Is(CustomRoles.Madmate) && target.Is(CustomRoleTypes.Impostor) && !Options.MadmateCanKillImp.GetBool())
return false;
//Sidekick can kill Sidekick
if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool())
return false;
//Recruit can kill Recruit
if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool())
return false;
//Sidekick can kill Sidekick
if (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool())
return false;
//Recruit can kill Recruit
if (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool())
return false;
if (Jackal.On && Jackal.ResetKillCooldownWhenSbGetKilled.GetBool() && !killer.Is(CustomRoles.Sidekick) && !target.Is(CustomRoles.Sidekick) && !killer.Is(CustomRoles.Jackal) && !target.Is(CustomRoles.Jackal) && !GameStates.IsMeeting)
Jackal.AfterPlayerDiedTask(killer);
if (target.Is(CustomRoles.Lucky))
{
var rd = IRandom.Instance;
if (rd.Next(0, 100) < Options.LuckyProbability.GetInt())
{
killer.SetKillCooldown(15f);
return false;
}
}
if (Main.ForCrusade.Contains(target.PlayerId))
{
foreach (PlayerControl player in Main.AllPlayerControls)
{
if (player.Is(CustomRoles.Crusader) && player.IsAlive() && !killer.Is(CustomRoles.Pestilence) && !killer.Is(CustomRoles.Minimalism))
{
player.Kill(killer);
Main.ForCrusade.Remove(target.PlayerId);
killer.RpcGuardAndKill(target);
return false;
}
if (player.Is(CustomRoles.Crusader) && player.IsAlive() && killer.Is(CustomRoles.Pestilence))
{
killer.Kill(player);
Main.ForCrusade.Remove(target.PlayerId);
target.RpcGuardAndKill(killer);
Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.PissedOff;
return false;
}
}
}
if (Aid.ShieldedPlayers.ContainsKey(target.PlayerId)) return false;
switch (target.GetCustomRole())
{
case CustomRoles.Medic:
Medic.IsDead(target);
break;
case CustomRoles.Monarch when killer.Is(CustomRoles.Knighted):
return false;
case CustomRoles.Gambler when Gambler.isShielded.ContainsKey(target.PlayerId):
killer.SetKillCooldown(time: 5f);
return false;
case CustomRoles.Spiritcaller:
if (Spiritcaller.InProtect(target))
{
killer.RpcGuardAndKill(target);
return false;
}
break;
}
if (Main.ShieldPlayer != byte.MaxValue && Main.ShieldPlayer == target.PlayerId && IsAllAlive)
{
Main.ShieldPlayer = byte.MaxValue;
killer.SetKillCooldown(15f);
killer.Notify(GetString("TriedToKillLastGameFirstKill"), 10f);
return false;
}
if (Options.MadmateSpawnMode.GetInt() == 1 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && target.CanBeMadmate())
{
Main.MadmateNum++;
target.RpcSetCustomRole(CustomRoles.Madmate);
ExtendedPlayerControl.RpcSetCustomRole(target.PlayerId, CustomRoles.Madmate);
target.Notify(ColorString(GetRoleColor(CustomRoles.Madmate), GetString("BecomeMadmateCuzMadmateMode")));
killer.SetKillCooldown();
killer.RpcGuardAndKill(target);
target.RpcGuardAndKill(killer);
target.RpcGuardAndKill(target);
Logger.Info("Add-on assigned:" + target?.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Madmate, "Assign " + CustomRoles.Madmate);
return false;
}
if (!Sentinel.OnAnyoneCheckMurder(killer))
return false;
if (!Main.PlayerStates[target.PlayerId].Role.OnCheckMurderAsTarget(killer, target)) return false;
if (!check) killer.Kill(target);
if (killer.Is(CustomRoles.Doppelganger)) (Main.PlayerStates[killer.PlayerId].Role as Doppelganger)?.OnCheckMurder(killer, target);
return true;
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
class MurderPlayerPatch
{
public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] MurderResultFlags resultFlags*/)
{
Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}{(target.IsProtected() ? " (Protected)" : string.Empty)}", "MurderPlayer");
if (RandomSpawn.CustomNetworkTransformPatch.NumOfTP.TryGetValue(__instance.PlayerId, out var num) && num > 2) RandomSpawn.CustomNetworkTransformPatch.NumOfTP[__instance.PlayerId] = 3;
if (!target.IsProtected() && !Doppelganger.DoppelVictim.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId))
{
Camouflage.ResetSkinAfterDeathPlayers.Add(target.PlayerId);
Camouflage.RpcSetSkin(target, ForceRevert: true);
}
}
public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
if (target.AmOwner) RemoveDisableDevicesPatch.UpdateDisableDevices();
if (!target.Data.IsDead || !AmongUsClient.Instance.AmHost) return;
if (Main.OverDeadPlayerList.Contains(target.PlayerId)) return;
PlayerControl killer = __instance; // Alternative variable
if (target.PlayerId == Godfather.GodfatherTarget) killer.RpcSetCustomRole(CustomRoles.Refugee);
PlagueDoctor.OnAnyMurder();
// Replacement process when the actual killer and killer are different
if (Sniper.TryGetSniper(target.PlayerId, ref killer))
{
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Sniped;
}
if (killer.Is(CustomRoles.Sniper))
if (!Options.UsePets.GetBool()) killer.RpcResetAbilityCooldown();
else Main.AbilityCD[killer.PlayerId] = (TimeStamp, Options.DefaultShapeshiftCooldown.GetInt());
if (killer != __instance)
{
Logger.Info($"Real Killer = {killer.GetNameWithRole().RemoveHtmlTags()}", "MurderPlayer");
}
if (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.etc)
{
//If the cause of death is not specified, it is determined as a normal kill.
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Kill;
}
//Let’s see if Youtuber was stabbed first
if (Main.FirstDied == byte.MaxValue && target.Is(CustomRoles.Youtuber))
{
CustomSoundsManager.RPCPlayCustomSoundAll("Congrats");
CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Youtuber);
CustomWinnerHolder.WinnerIds.Add(target.PlayerId);
}
//Record the first blow
if (Main.FirstDied == byte.MaxValue)
Main.FirstDied = target.PlayerId;
Postman.CheckAndResetTargets(target, isDeath: true);
if (target.Is(CustomRoles.Trapper) && killer != target)
killer.TrapperKilled(target);
//if ((Romantic.BetPlayer.TryGetValue(target.PlayerId, out var RomanticPartner) && target.PlayerId == RomanticPartner) && target.PlayerId != killer.PlayerId)
// VengefulRomantic.PartnerKiller.Add(killer.PlayerId, 1);
Witness.AllKillers.Remove(killer.PlayerId);
Witness.AllKillers.Add(killer.PlayerId, TimeStamp);
killer.AddKillTimerToDict();
switch (target.GetCustomRole())
{
case CustomRoles.BallLightning:
if (killer != target) BallLightning.MurderPlayer(killer, target);
break;
case CustomRoles.Altruist:
if (killer != target) Altruist.OnKilled(killer);
break;
}
Main.PlayerStates[killer.PlayerId].Role.OnMurder(killer, target);
if (killer.Is(CustomRoles.TicketsStealer) && killer.PlayerId != target.PlayerId)
killer.Notify(string.Format(GetString("TicketsStealerGetTicket"), ((Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * Options.TicketsPerKill.GetFloat()).ToString("0.0#####")));
if (killer.Is(CustomRoles.Pickpocket) && killer.PlayerId != target.PlayerId)
killer.Notify(string.Format(GetString("PickpocketGetVote"), ((Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * Pickpocket.VotesPerKill.GetFloat()).ToString("0.0#####")));
if (target.Is(CustomRoles.Avanger))
{
var pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToArray();
var rp = pcList[IRandom.Instance.Next(0, pcList.Length)];
if (!rp.Is(CustomRoles.Pestilence))
{
rp.Suicide(PlayerState.DeathReason.Revenge, target);
}
}
if (target.Is(CustomRoles.Bait))
{
if (killer.PlayerId != target.PlayerId || (target.GetRealKiller()?.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith) || !killer.Is(CustomRoles.Oblivious) || !Options.ObliviousBaitImmune.GetBool())
{
killer.RPCPlayCustomSound("Congrats");
target.RPCPlayCustomSound("Congrats");
float delay;
if (Options.BaitDelayMax.GetFloat() < Options.BaitDelayMin.GetFloat()) delay = 0f;
else delay = IRandom.Instance.Next((int)Options.BaitDelayMin.GetFloat(), (int)Options.BaitDelayMax.GetFloat() + 1);
delay = Math.Max(delay, 0.15f);
if (delay > 0.15f && Options.BaitDelayNotify.GetBool()) killer.Notify(ColorString(GetRoleColor(CustomRoles.Bait), string.Format(GetString("KillBaitNotify"), (int)delay)), delay);
Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} 击杀诱饵 => {target.GetNameWithRole().RemoveHtmlTags()}", "MurderPlayer");
_ = new LateTask(() =>
{
if (GameStates.IsInTask)
{
if (!Options.ReportBaitAtAllCost.GetBool())
{
killer.CmdReportDeadBody(target.Data);
}
else
{
killer.NoCheckStartMeeting(target.Data, force: true);
}
}
}, delay, "Bait Self Report");
}
}
foreach (var pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Mediumshiper)))
{
pc.Notify(ColorString(GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshiperKnowPlayerDead")));
}
AfterPlayerDeathTasks(target);
Main.PlayerStates[target.PlayerId].SetDead();
target.SetRealKiller(killer, true);
CountAlivePlayers(true);
Camouflager.IsDead(target);
TargetDies(__instance, target);
if (Options.LowLoadMode.GetBool())
{
__instance.MarkDirtySettings();
target.MarkDirtySettings();
NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer);
NotifyRoles(SpecifySeer: target);
}
else
{
SyncAllSettings();
NotifyRoles(ForceLoop: true);
}
}
}
// Triggered when the shapeshifter selects a target
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckShapeshift))]
class CheckShapeshiftPatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] bool shouldAnimate*/)
{
return ShapeshiftPatch.ProcessShapeshift(__instance, target); // return false to cancel the shapeshift
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckShapeshift))]
class CmdCheckShapeshiftPatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] bool shouldAnimate*/)
{
return CheckShapeshiftPatch.Prefix(__instance, target /*, shouldAnimate*/);
}
}
// Triggered when the egg animation starts playing
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Shapeshift))]
class ShapeshiftPatch
{
public static bool ProcessShapeshift(PlayerControl shapeshifter, PlayerControl target)
{
if (!Main.ProcessShapeshifts) return true;
Logger.Info($"{shapeshifter?.GetNameWithRole()} => {target?.GetNameWithRole()}", "Shapeshift");
if (shapeshifter == null || target == null) return true;
var shapeshifting = shapeshifter.PlayerId != target.PlayerId;
if (Main.CheckShapeshift.TryGetValue(shapeshifter.PlayerId, out var last) && last == shapeshifting)
{
// Dunno how you would get here but ok
return true;
}
Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting;
Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId;
if (!AmongUsClient.Instance.AmHost) return true;
if (!shapeshifting) Camouflage.RpcSetSkin(shapeshifter);
bool isSSneeded = true;
if (!Pelican.IsEaten(shapeshifter.PlayerId) && !GameStates.IsVoting)
{
isSSneeded = Main.PlayerStates[shapeshifter.PlayerId].Role.OnShapeshift(shapeshifter, target, shapeshifting);
}
if (shapeshifter.Is(CustomRoles.Hangman) && shapeshifter.GetAbilityUseLimit() < 1 && shapeshifting) shapeshifter.SetKillCooldown(Hangman.ShapeshiftDuration.GetFloat() + 1f);
if (shapeshifter.Is(CustomRoles.Camouflager) && !shapeshifting) Camouflager.Reset();
// Forced rewriting in case the name cannot be corrected due to the timing of canceling the transformation being off.
if (!shapeshifting && !shapeshifter.Is(CustomRoles.Glitch) && isSSneeded)
{
_ = new LateTask(() => { NotifyRoles(NoCache: true); },
1.2f, "ShapeShiftNotify");
}
if ((!shapeshifting || !isSSneeded) && !Swapster.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId))
{
_ = new LateTask(shapeshifter.RpcResetAbilityCooldown, 0.01f, log: false);
}
if (!isSSneeded)
{
Main.CheckShapeshift[shapeshifter.PlayerId] = false;
shapeshifter.RpcRejectShapeshift();
NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter);
}
return isSSneeded || !Options.DisableShapeshiftAnimations.GetBool() || !shapeshifting;
}
// Tasks that should run when someone performs a shapeshift (with the egg animation) should be here.
public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
if (!Main.ProcessShapeshifts || !GameStates.IsInTask || __instance == null || target == null) return;
bool shapeshifting = __instance.PlayerId != target.PlayerId;
foreach (var pc in Main.AllAlivePlayerControls)
{
if (pc.Is(CustomRoles.Shiftguard))
{
pc.Notify(shapeshifting ? GetString("ShiftguardNotifySS") : "ShiftguardNotifyUnshift");
}
}
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))]
class ReportDeadBodyPatch
{
public static Dictionary<byte, bool> CanReport;
public static Dictionary<byte, List<GameData.PlayerInfo>> WaitReport = [];
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameData.PlayerInfo target)
{
if (GameStates.IsMeeting) return false;
if (Options.DisableMeeting.GetBool()) return false;
if (Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato) return false;
if (!CanReport[__instance.PlayerId])
{
WaitReport[__instance.PlayerId].Add(target);
Logger.Warn($"{__instance.GetNameWithRole().RemoveHtmlTags()}: Reporting is currently prohibited, so we will wait until it becomes possible.", "ReportDeadBody");
return false;
}
Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target?.Object?.GetNameWithRole() ?? "null"}", "ReportDeadBody");
foreach (var kvp in Main.PlayerStates)
{
kvp.Value.LastRoom = GetPlayerById(kvp.Key).GetPlainShipRoom();
}
if (!AmongUsClient.Instance.AmHost) return true;
try
{
// If the caller is dead, this process will cancel the meeting, so stop here.
if (__instance.Data.IsDead) return false;
//=============================================
// Next, check whether this meeting is allowed
//=============================================
var killer = target?.Object?.GetRealKiller();
var killerRole = killer?.GetCustomRole();
if (((IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool() && (Main.NormalOptions.MapId != 5 || !Options.CommsCamouflageDisableOnFungle.GetBool())) || Camouflager.IsActive) && Options.DisableReportWhenCC.GetBool()) return false;
if (target == null)
{
if (__instance.Is(CustomRoles.Jester) && !Options.JesterCanUseButton.GetBool()) return false;
if (__instance.Is(CustomRoles.NiceSwapper) && !NiceSwapper.CanStartMeeting.GetBool()) return false;
if (SoulHunter.IsSoulHunterTarget(__instance.PlayerId))
{
__instance.Notify(GetString("SoulHunterTargetNotifyNoMeeting"));
_ = new LateTask(() =>
{
if (SoulHunter.IsSoulHunterTarget(__instance.PlayerId)) __instance.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter.GetSoulHunter(__instance.PlayerId).SoulHunter_.GetRealName()), 300f);
}, 4f, log: false);
return false;
}
}
if (target != null)
{
if (Bloodhound.UnreportablePlayers.Contains(target.PlayerId)
|| Vulture.UnreportablePlayers.Contains(target.PlayerId)) return false;
if (!Librarian.OnAnyoneReport(__instance)) return false;
if (!Main.PlayerStates[__instance.PlayerId].Role.CheckReportDeadBody(__instance, target, killer)) return false;
if (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Gambled
|| killerRole == CustomRoles.Scavenger
|| Main.CleanerBodies.Contains(target.PlayerId)
|| Main.MedusaBodies.Contains(target.PlayerId)) return false;
var tpc = GetPlayerById(target.PlayerId);
if (__instance.Is(CustomRoles.Oblivious))
{
if (!tpc.Is(CustomRoles.Bait) || Options.ObliviousBaitImmune.GetBool()) /* && (target?.Object != null)*/
{
return false;
}
}
if (__instance.Is(CustomRoles.Unlucky) && (target.Object == null || !target.Object.Is(CustomRoles.Bait)))
{
var Ue = IRandom.Instance;
if (Ue.Next(0, 100) < Options.UnluckyReportSuicideChance.GetInt())
{
__instance.Suicide();
return false;
}
}
if (BoobyTrap.BoobyTrapBody.Contains(target.PlayerId) && __instance.IsAlive())
{
if (!Options.TrapOnlyWorksOnTheBodyBoobyTrap.GetBool())
{
var killerID = BoobyTrap.KillerOfBoobyTrapBody[target.PlayerId];
__instance.Suicide(PlayerState.DeathReason.Bombed, GetPlayerById(killerID));
RPC.PlaySoundRPC(killerID, Sounds.KillSound);
if (!BoobyTrap.BoobyTrapBody.Contains(__instance.PlayerId)) BoobyTrap.BoobyTrapBody.Add(__instance.PlayerId);
BoobyTrap.KillerOfBoobyTrapBody.TryAdd(__instance.PlayerId, killerID);
return false;
}
var killerID2 = target.PlayerId;
__instance.Suicide(PlayerState.DeathReason.Bombed, GetPlayerById(killerID2));
RPC.PlaySoundRPC(killerID2, Sounds.KillSound);
return false;
}
if (tpc.Is(CustomRoles.Unreportable)) return false;
}
if (Options.SyncButtonMode.GetBool() && target == null)
{
Logger.Info("最大:" + Options.SyncedButtonCount.GetInt() + ", 現在:" + Options.UsedButtonCount, "ReportDeadBody");
if (Options.SyncedButtonCount.GetFloat() <= Options.UsedButtonCount)
{
Logger.Info("使用可能ボタン回数が最大数を超えているため、ボタンはキャンセルされました。", "ReportDeadBody");
return false;
}
Options.UsedButtonCount++;
if (Math.Abs(Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) < 0.5f)
{
Logger.Info("使用可能ボタン回数が最大数に達しました。", "ReportDeadBody");
}
}
AfterReportTasks(__instance, target);
}
catch (Exception e)
{
Logger.Exception(e, "ReportDeadBodyPatch");
Logger.SendInGame("Error: " + e);
}
return true;
}
public static void AfterReportTasks(PlayerControl player, GameData.PlayerInfo target)
{
//====================================================================================
// Hereinafter, it is assumed that it is confirmed that the button is pressed.
//====================================================================================
Damocles.countRepairSabotage = false;
Stressed.countRepairSabotage = false;
if (target == null)
{
if (player.Is(CustomRoles.Mayor))
{
Main.MayorUsedButtonCount[player.PlayerId] += 1;
}
}
else
{
var tpc = GetPlayerById(target.PlayerId);
if (tpc != null && !tpc.IsAlive())
{
if (player.Is(CustomRoles.Detective) && player.PlayerId != target.PlayerId)
{
Detective.OnReportDeadBody(player, target.Object);
}
else if (player.Is(CustomRoles.Sleuth) && player.PlayerId != target.PlayerId)
{
string msg = string.Format(GetString("SleuthMsg"), tpc.GetRealName(), tpc.GetDisplayRoleName());
Main.SleuthMsgs[player.PlayerId] = msg;
}
}
if (Main.InfectedBodies.Contains(target.PlayerId)) Virus.OnKilledBodyReport(player);
}
Enigma.OnReportDeadBody(player, target);
Mediumshiper.OnReportDeadBody(target);
Mortician.OnReportDeadBody(player, target);
Spiritualist.OnReportDeadBody(target);
Main.LastVotedPlayerInfo = null;
Witness.AllKillers.Clear();
Main.ArsonistTimer.Clear();
Farseer.FarseerTimer.Clear();
Puppeteer.PuppeteerList.Clear();
Puppeteer.PuppeteerDelayList.Clear();
Main.TaglockedList.Clear();
Main.GuesserGuessed.Clear();
Main.VeteranInProtect.Clear();
Grenadier.GrenadierBlinding.Clear();
Main.BlockSabo.Clear();
Main.BlockedVents.Clear();
Grenadier.MadGrenadierBlinding.Clear();
Divinator.didVote.Clear();
Oracle.didVote.Clear();
Vulture.Clear();
foreach (var state in Main.PlayerStates.Values)
{
if (state.Role.IsEnable)
{
state.Role.OnReportDeadBody();
}
}
Main.AbilityCD.Clear();
if (player.Is(CustomRoles.Damocles))
{
Damocles.OnReport(player.PlayerId);
}
Damocles.OnMeetingStart();
if (player.Is(CustomRoles.Stressed))
{
Stressed.OnReport(player);
}
Stressed.OnMeetingStart();
foreach (var pc in Main.AllPlayerControls)
{
if (Main.CheckShapeshift.ContainsKey(pc.PlayerId) && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId))
{
Camouflage.RpcSetSkin(pc, RevertToDefault: true);
}
}
MeetingTimeManager.OnReportDeadBody();
NameNotifyManager.Reset();
NotifyRoles(isForMeeting: true, NoCache: true, CamouflageIsForMeeting: true, GuesserIsForMeeting: true);
_ = new LateTask(SyncAllSettings, 3f, "SyncAllSettings on meeting start");
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
class FixedUpdatePatch
{
private static readonly StringBuilder Mark = new(20);
private static readonly StringBuilder Suffix = new(120);
private static int LevelKickBufferTime = 10;
private static readonly Dictionary<byte, int> BufferTime = [];
private static readonly Dictionary<byte, int> DeadBufferTime = [];
private static readonly Dictionary<byte, long> LastUpdate = [];
private static long LastAddAbilityTime;
public static async void Postfix(PlayerControl __instance)
{
if (__instance == null) return;
if (!GameStates.IsModHost) return;
byte id = __instance.PlayerId;
if (AmongUsClient.Instance.AmHost && GameStates.IsInTask && ReportDeadBodyPatch.CanReport[id] && ReportDeadBodyPatch.WaitReport[id].Count > 0)
{
if (Glitch.hackedIdList.ContainsKey(id))
{
__instance.Notify(string.Format(GetString("HackedByGlitch"), "Report"));
Logger.Info("Dead Body Report Blocked (player is hacked by Glitch)", "FixedUpdate.ReportDeadBody");
ReportDeadBodyPatch.WaitReport[id].Clear();
}
else
{
var info = ReportDeadBodyPatch.WaitReport[id][0];
ReportDeadBodyPatch.WaitReport[id].Clear();
Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}:通報可能になったため通報処理を行います", "ReportDeadbody");
__instance.ReportDeadBody(info);
}
}
if (Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive())
{
DeadBufferTime.TryAdd(id, IRandom.Instance.Next(50, 70));
DeadBufferTime[id]--;
if (DeadBufferTime[id] > 0) return;
DeadBufferTime[id] = IRandom.Instance.Next(50, 70);
}
if (Options.LowLoadMode.GetBool())
{
BufferTime.TryAdd(id, 10);
BufferTime[id]--;
if (BufferTime[id] % 2 == 1 && Options.DeepLowLoad.GetBool()) return;
}
//if (Options.DeepLowLoad.GetBool()) await Task.Run(() => { DoPostfix(__instance); });
/*else */
try
{
await DoPostfix(__instance);
}
catch (Exception ex)
{
Logger.Error($"Error for {__instance.GetNameWithRole()}: {ex}", "FixedUpdatePatch");
}
}
public static Task DoPostfix(PlayerControl __instance)
{
var player = __instance;
var playerId = player.PlayerId;
bool lowLoad = false;
if (Options.LowLoadMode.GetBool())
{
if (BufferTime[playerId] > 0) lowLoad = true;
else BufferTime[playerId] = 10;
}
if (!lowLoad)
{
Zoom.OnFixedUpdate();
NameNotifyManager.OnFixedUpdate(player);
TargetArrow.OnFixedUpdate(player);
LocateArrow.OnFixedUpdate(player);
if (RPCHandlerPatch.ReportDeadBodyRPCs.Remove(playerId))
Logger.Info($"Cleared ReportDeadBodyRPC Count for {player.GetRealName().RemoveHtmlTags()}", "FixedUpdatePatch");
}
if (AmongUsClient.Instance.AmHost)
{
if (GameStates.IsLobby && ((ModUpdater.hasUpdate && ModUpdater.forceUpdate) || ModUpdater.isBroken || !Main.AllowPublicRoom) && AmongUsClient.Instance.IsGamePublic)
AmongUsClient.Instance.ChangeGamePublic(false);
// Kick low level people
if (!lowLoad && GameStates.IsLobby && !player.AmOwner && Options.KickLowLevelPlayer.GetInt() != 0 && (
(player.Data.PlayerLevel != 0 && player.Data.PlayerLevel < Options.KickLowLevelPlayer.GetInt()) ||
player.Data.FriendCode == string.Empty
))
{
LevelKickBufferTime--;
if (LevelKickBufferTime <= 0)
{
LevelKickBufferTime = 20;
if (player.GetClient().ProductUserId != "")
{
if (!BanManager.TempBanWhiteList.Contains(player.GetClient().GetHashedPuid()))
BanManager.TempBanWhiteList.Add(player.GetClient().GetHashedPuid());
}
string msg = string.Format(GetString("KickBecauseLowLevel"), player.GetRealName().RemoveHtmlTags());
Logger.SendInGame(msg);
AmongUsClient.Instance.KickPlayer(player.GetClientId(), true);
Logger.Info(msg, "Low Level Temp Ban");
}
}
if (!Main.KillTimers.TryAdd(playerId, 10f) && ((!player.inVent && !player.MyPhysics.Animations.IsPlayingEnterVentAnimation()) || player.Is(CustomRoles.Haste)) && Main.KillTimers[playerId] > 0)
{
Main.KillTimers[playerId] -= Time.fixedDeltaTime;
}
if (!lowLoad && player.IsModClient() && player.Is(CustomRoles.Haste)) player.ForceKillTimerContinue = true;
if (DoubleTrigger.FirstTriggerTimer.Count > 0) DoubleTrigger.OnFixedUpdate(player);
if (Main.PlayerStates.TryGetValue(playerId, out var s) && s.Role.IsEnable)
{
s.Role.OnFixedUpdate(player);
}
if (GameStates.IsInTask && player.Is(CustomRoles.PlagueBearer) && PlagueBearer.IsPlaguedAll(player))
{
player.RpcSetCustomRole(CustomRoles.Pestilence);
player.Notify(GetString("PlagueBearerToPestilence"));
player.RpcGuardAndKill(player);
if (!PlagueBearer.PestilenceList.Contains(playerId))
PlagueBearer.PestilenceList.Add(playerId);
player.ResetKillCooldown();
PlagueBearer.playerIdList.Remove(playerId);
}
if (GameStates.IsInTask && player != null && player.IsAlive() && !Pelican.IsEaten(player.PlayerId))
{
foreach (var state in Main.PlayerStates.Values)
{
if (state.Role.IsEnable)
{
state.Role.OnCheckPlayerPosition(player);
}
}
Asthmatic.OnCheckPlayerPosition(player);
}
if (!lowLoad && Main.PlayerStates.TryGetValue(playerId, out var playerState) && GameStates.IsInTask)
{
var subRoles = playerState.SubRoles;
if (subRoles.Contains(CustomRoles.Damocles)) Damocles.Update(player);
if (subRoles.Contains(CustomRoles.Stressed)) Stressed.Update(player);
if (subRoles.Contains(CustomRoles.Asthmatic)) Asthmatic.OnFixedUpdate();
if (subRoles.Contains(CustomRoles.Disco)) Disco.OnFixedUpdate(player);
}
long now = TimeStamp;
if (!lowLoad && Options.UsePets.GetBool() && GameStates.IsInTask && (!LastUpdate.TryGetValue(playerId, out var lastPetNotify) || lastPetNotify < now))
{
if (Main.AbilityCD.TryGetValue(playerId, out var timer))
{
if (timer.START_TIMESTAMP + timer.TOTALCD < TimeStamp || !player.IsAlive())
{
Main.AbilityCD.Remove(playerId);
}
if (!player.IsModClient()) NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
LastUpdate[playerId] = now;
}
}
foreach (var state in Main.PlayerStates.Values)
{
if (state.Role.IsEnable)
{
state.Role.OnGlobalFixedUpdate(player, lowLoad);
}
}
if (!lowLoad) Randomizer.OnFixedUpdateForPlayers(player);
}
if (!lowLoad)
{
long now = TimeStamp;
// Ability Use Gain every 5 seconds
if (GameStates.IsInTask && player.IsAlive() && Main.PlayerStates.TryGetValue(playerId, out var state) && state.TaskState.IsTaskFinished && LastAddAbilityTime + 5 < now)
{
LastAddAbilityTime = now;
if (player.Is(CustomRoles.SabotageMaster))
{
var sm = Main.PlayerStates[playerId].Role as SabotageMaster;
sm.UsedSkillCount -= SabotageMaster.AbilityChargesWhenFinishedTasks.GetFloat();
sm.SendRPC();
}
else
{
float add = player.GetCustomRole() switch
{
CustomRoles.Ventguard => Options.VentguardAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Grenadier => Options.GrenadierAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Lighter => Options.LighterAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.SecurityGuard => Options.SecurityGuardAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.DovesOfNeace => Options.DovesOfNeaceAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.TimeMaster => Options.TimeMasterAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Veteran => Options.VeteranAbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Bloodhound => Bloodhound.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.CameraMan => CameraMan.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Chameleon => Chameleon.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Convener => Convener.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Divinator => Divinator.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Doormaster => Doormaster.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Drainer => Drainer.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Druid => Druid.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Judge => Judge.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Mediumshiper => Mediumshiper.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.NiceSwapper => NiceSwapper.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Oracle => Oracle.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.ParityCop => ParityCop.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Perceiver => Perceiver.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Ricochet => Ricochet.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Spy => Spy.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Tether => Tether.AbilityChargesWhenFinishedTasks.GetFloat(),
CustomRoles.Tracker => Tracker.AbilityChargesWhenFinishedTasks.GetFloat(),
_ => float.MaxValue,
};
if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0) player.RpcIncreaseAbilityUseLimitBy(add);
}
}
if (Witness.AllKillers.TryGetValue(playerId, out var ktime) && ktime + Options.WitnessTime.GetInt() < now) Witness.AllKillers.Remove(playerId);
if (GameStates.IsInTask && player.IsAlive() && Options.LadderDeath.GetBool()) FallFromLadder.FixedUpdate(player);
if (GameStates.IsInGame) LoversSuicide();
if (GameStates.IsInTask && player == PlayerControl.LocalPlayer && Options.DisableDevices.GetBool())
{
DisableDevice.FixedUpdate();
}
if (GameStates.IsInGame && Main.RefixCooldownDelay <= 0)
{
foreach (PlayerControl pc in Main.AllPlayerControls)
{
if (pc.Is(CustomRoles.Vampire) || pc.Is(CustomRoles.Warlock) || pc.Is(CustomRoles.Assassin) || pc.Is(CustomRoles.Undertaker) || pc.Is(CustomRoles.Poisoner))
Main.AllPlayerKillCooldown[pc.PlayerId] = Options.DefaultKillCooldown * 2;
}
}
if (!Main.DoBlockNameChange && AmongUsClient.Instance.AmHost)
ApplySuffix(__instance);
}
if (__instance.AmOwner)
{
if ((Main.ChangedRole && __instance == PlayerControl.LocalPlayer && AmongUsClient.Instance.AmHost) || (GameStates.IsInTask && !__instance.Is(CustomRoleTypes.Impostor) && __instance.CanUseKillButton() && !__instance.Data.IsDead))
{
var players = __instance.GetPlayersInAbilityRangeSorted();
PlayerControl closest = players.Count == 0 ? null : players[0];
HudManager.Instance.KillButton.SetTarget(closest);
}
}
var RoleTextTransform = __instance.cosmetics.nameText.transform.Find("RoleText");
var RoleText = RoleTextTransform.GetComponent<TextMeshPro>();
if (RoleText != null && __instance != null && !lowLoad)
{
if (GameStates.IsLobby)
{
if (Main.playerVersion.TryGetValue(playerId, out var ver))
{
if (Main.ForkId != ver.forkId)
__instance.cosmetics.nameText.text = $"<color=#ff0000><size=1.2>{ver.forkId}</size>\n{__instance?.name}</color>";
else if (Main.version.CompareTo(ver.version) == 0)
__instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"<color=#87cefa>{__instance.name}</color>" : $"<color=#ffff00><size=1.2>{ver.tag}</size>\n{__instance?.name}</color>";
else __instance.cosmetics.nameText.text = $"<color=#ff0000><size=1.2>v{ver.version}</size>\n{__instance?.name}</color>";
}
else __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName;
}
if (GameStates.IsInGame)
{
var RoleTextData = GetRoleText(PlayerControl.LocalPlayer.PlayerId, playerId);
RoleText.text = RoleTextData.Item1;
RoleText.color = RoleTextData.Item2;
if (Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.SoloKombat or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato) RoleText.text = string.Empty;
RoleText.enabled = IsRoleTextEnabled(__instance);
if (!PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) && __instance.Is(CustomRoles.Trickster))
{
RoleText.text = Farseer.RandomRole[PlayerControl.LocalPlayer.PlayerId];
RoleText.text += Farseer.GetTaskState();
}
if (!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay)
{
RoleText.enabled = false;
if (!__instance.AmOwner) __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName;
}
bool isProgressTextLong = false;
var progressText = GetProgressText(__instance);
if (progressText.RemoveHtmlTags().Length > 25 && Main.VisibleTasksCount)
{
isProgressTextLong = true;
progressText = $"\n{progressText}";
}
if (Main.VisibleTasksCount)
RoleText.text += progressText;
var seer = PlayerControl.LocalPlayer;
var target = __instance;
Mark.Clear();
Suffix.Clear();
string RealName = target.GetRealName();
if (target.AmOwner && GameStates.IsInTask)
{
//targetが自分自身
if (target.Is(CustomRoles.Arsonist) && target.IsDouseDone())
RealName = ColorString(GetRoleColor(CustomRoles.Arsonist), GetString("EnterVentToWin"));
else if (target.Is(CustomRoles.Revolutionist) && target.IsDrawDone())
RealName = ColorString(GetRoleColor(CustomRoles.Revolutionist), string.Format(GetString("EnterVentWinCountDown"), Main.RevolutionistCountdown.GetValueOrDefault(seer.PlayerId, 10)));
if (Pelican.IsEaten(seer.PlayerId))
RealName = ColorString(GetRoleColor(CustomRoles.Pelican), GetString("EatenByPelican"));
switch (Options.CurrentGameMode)
{
case CustomGameMode.SoloKombat:
SoloKombatManager.GetNameNotify(target, ref RealName);
break;
case CustomGameMode.FFA:
FFAManager.GetNameNotify(target, ref RealName);
break;
}
if (Deathpact.IsInActiveDeathpact(seer))
RealName = Deathpact.GetDeathpactString(seer);
if (NameNotifyManager.GetNameNotify(target, out var name))
RealName = name;
}
// Name Color Manager
RealName = RealName.ApplyNameColorData(seer, target, false);
if (seer.GetCustomRole().IsCrewmate() && seer.Is(CustomRoles.Madmate) && Marshall.MadmateCanFindMarshall) //seerがインポスター
{
if (target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) //targetがタスクを終わらせたマッドスニッチ
Mark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); //targetにマーク付与
}
switch (target.GetCustomRole()) //seerがインポスター
{
case CustomRoles.Snitch when seer.GetCustomRole().IsImpostor() && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished:
Mark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); //targetにマーク付与
break;
case CustomRoles.Marshall when seer.GetCustomRole().IsCrewmate() && target.GetTaskState().IsTaskFinished:
Mark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); //targetにマーク付与
break;
case CustomRoles.SuperStar when Options.EveryOneKnowSuperStar.GetBool():
Mark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★"));
break;
case CustomRoles.PlagueDoctor:
Mark.Append(PlagueDoctor.GetMarkOthers(seer, target));
Suffix.Append(PlagueDoctor.GetLowerTextOthers(seer, target));
break;
case CustomRoles.EvilTracker:
Suffix.Append(EvilTracker.GetTargetArrow(seer, target));
break;
}
switch (seer.GetCustomRole())
{
case CustomRoles.Lookout:
if (seer.IsAlive() && target.IsAlive())
{
Mark.Append(ColorString(GetRoleColor(CustomRoles.Lookout), " " + target.PlayerId) + " ");
}
break;
case CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId):
Mark.Append($"<color={GetRoleColorCode(CustomRoles.PlagueBearer)}>●</color>");
// PlagueBearer.SendRPC(seer, target);
break;
case CustomRoles.Arsonist:
if (seer.IsDousedPlayer(target))
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Arsonist)}>▲</color>");
}
else if (
Main.currentDousingTarget != byte.MaxValue &&
Main.currentDousingTarget == target.PlayerId
)
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Arsonist)}>△</color>");
}
break;
case CustomRoles.Revolutionist:
if (seer.IsDrawPlayer(target))
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Revolutionist)}>●</color>");
}
else if (
Main.currentDrawTarget != byte.MaxValue &&
Main.currentDrawTarget == target.PlayerId
)
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Revolutionist)}>○</color>");
}
break;
case CustomRoles.Farseer:
if (Main.currentDrawTarget != byte.MaxValue &&
Main.currentDrawTarget == target.PlayerId)
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Farseer)}>○</color>");
}
break;
case CustomRoles.Analyzer:
if ((Main.PlayerStates[seer.PlayerId].Role as Analyzer).CurrentTarget.ID == target.PlayerId)
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Analyzer)}>○</color>");
}
break;
case CustomRoles.Puppeteer:
if (Puppeteer.PuppeteerList.ContainsValue(seer.PlayerId) && Puppeteer.PuppeteerList.ContainsKey(target.PlayerId))
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Impostor)}>◆</color>");
}
break;
case CustomRoles.EvilTracker:
Mark.Append(EvilTracker.GetTargetMark(seer, target));
break;
case CustomRoles.Tracker:
Mark.Append(Tracker.GetTargetMark(seer, target));
Suffix.Append(Tracker.GetTrackerArrow(seer, target));
break;
case CustomRoles.AntiAdminer when GameStates.IsInTask:
(Main.PlayerStates[seer.PlayerId].Role as AntiAdminer).OnFixedUpdate(seer);
if (target.AmOwner)
{
if (AntiAdminer.IsAdminWatch) Suffix.Append(GetString("AntiAdminerAD"));
if (AntiAdminer.IsVitalWatch) Suffix.Append(GetString("AntiAdminerVI"));
if (AntiAdminer.IsDoorLogWatch) Suffix.Append(GetString("AntiAdminerDL"));
if (AntiAdminer.IsCameraWatch) Suffix.Append(GetString("AntiAdminerCA"));
}
break;
case CustomRoles.Executioner:
Mark.Append(Executioner.TargetMark(seer, target));
break;
case CustomRoles.Gamer:
Mark.Append(Gamer.TargetMark(seer, target));
break;
case CustomRoles.BountyHunter:
Suffix.Append(BountyHunter.GetTargetArrow(seer, target));
break;
case CustomRoles.Mortician:
Suffix.Append(Mortician.GetTargetArrow(seer, target));
break;
case CustomRoles.Bloodhound:
Suffix.Append(Bloodhound.GetTargetArrow(seer, target));
break;
case CustomRoles.Stealth:
Suffix.Append(Stealth.GetSuffix(seer, target));
break;
case CustomRoles.Snitch:
Suffix.Append(Snitch.GetSnitchArrow(seer, target));
break;
case CustomRoles.VengefulRomantic when seer.PlayerId == target.PlayerId:
Suffix.Append(VengefulRomantic.GetTargetText(seer.PlayerId));
break;
case CustomRoles.Romantic when seer.PlayerId == target.PlayerId:
Suffix.Append(Romantic.GetTargetText(seer.PlayerId));
break;
case CustomRoles.Ricochet when seer.PlayerId == target.PlayerId:
Suffix.Append(Ricochet.TargetText(seer.PlayerId));
break;
case CustomRoles.Hitman when seer.PlayerId == target.PlayerId:
Suffix.Append(Hitman.GetTargetText(seer.PlayerId));
break;
case CustomRoles.Penguin when target.PlayerId == seer.PlayerId:
Suffix.Append(Penguin.GetSuffix(seer));
break;
case CustomRoles.Changeling when target.PlayerId == seer.PlayerId:
Suffix.Append(Changeling.GetSuffix(seer));
break;
case CustomRoles.Tiger when target.PlayerId == seer.PlayerId:
Suffix.Append(Tiger.GetSuffix(seer));
break;
}
Mark.Append(Totocalcio.TargetMark(seer, target));
Mark.Append(Romantic.TargetMark(seer, target));
Mark.Append(Lawyer.LawyerMark(seer, target));
Mark.Append(Marshall.GetWarningMark(seer, target));
if (Randomizer.IsShielded(target)) Mark.Append(ColorString(GetRoleColor(CustomRoles.Randomizer), "✚"));
if (target.AmOwner) Mark.Append(Sniper.GetShotNotify(target.PlayerId));
if (BallLightning.IsGhost(target)) Mark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■"));
if (Medic.ProtectList.Count > 0)
{
var shieldMark = $"<color={GetRoleColorCode(CustomRoles.Medic)}> ●</color>";
bool seerIsMedic = seer.Is(CustomRoles.Medic);
bool targetProtected = Medic.InProtect(target.PlayerId);
if (seer.PlayerId == target.PlayerId && (Medic.InProtect(seer.PlayerId) || Medic.TempMarkProtected == seer.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() is 0 or 2))
Mark.Append(shieldMark);
else if (seerIsMedic && (targetProtected || Medic.TempMarkProtected == target.PlayerId) && (Medic.WhoCanSeeProtect.GetInt() is 0 or 1))
Mark.Append(shieldMark);
else if (seer.Data.IsDead && targetProtected && !seerIsMedic)
Mark.Append(shieldMark);
}
Mark.Append(Snitch.GetWarningArrow(seer, target));
Mark.Append(Snitch.GetWarningMark(seer, target));
if (Main.LoversPlayers.Count > 0)
{
if (__instance.Is(CustomRoles.Lovers))
{
if (PlayerControl.LocalPlayer.Is(CustomRoles.Lovers))
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Lovers)}>♥</color>");
}
else if (PlayerControl.LocalPlayer.Data.IsDead)
{
Mark.Append($"<color={GetRoleColorCode(CustomRoles.Lovers)}>♥</color>");
}
}
}
Suffix.Append(Bubble.GetEncasedPlayerSuffix(seer, target));
Suffix.Append(Randomizer.GetSuffixText(seer, target));
Suffix.Append(Deathpact.GetDeathpactPlayerArrow(seer, target));
Suffix.Append(Deathpact.GetDeathpactMark(seer, target));
if (seer.Is(CustomRoles.Asthmatic) && seer.PlayerId == target.PlayerId) Suffix.Append(Asthmatic.GetSuffixText(seer.PlayerId));
if (target.Is(CustomRoles.Librarian)) Suffix.Append(Librarian.GetNameTextForSuffix(target.PlayerId));
Suffix.Append(Spiritualist.GetSpiritualistArrow(seer, target));
switch (Options.CurrentGameMode)
{
case CustomGameMode.FFA:
Suffix.Append(FFAManager.GetPlayerArrow(seer, target));
if (seer.PlayerId == target.PlayerId) Suffix.Append((Suffix.Length > 0 && FFAManager.LatestChatMessage != string.Empty ? "\n" : string.Empty) + FFAManager.LatestChatMessage);
break;
case CustomGameMode.MoveAndStop when seer.PlayerId == target.PlayerId:
Suffix.Append(MoveAndStopManager.GetSuffixText(seer));
break;
case CustomGameMode.HotPotato when !seer.IsModClient() && seer.PlayerId == target.PlayerId && seer.IsAlive():
Suffix.Append(HotPotatoManager.GetSuffixText(seer.PlayerId));
break;
}
if (Vulture.ArrowsPointingToDeadBody.GetBool())
Suffix.Append(Vulture.GetTargetArrow(seer, target));
Suffix.Append(Tracefinder.GetTargetArrow(seer, target));
if (Options.CurrentGameMode == CustomGameMode.SoloKombat)
Suffix.Append(SoloKombatManager.GetDisplayHealth(target));
/*if(main.AmDebugger.Value && main.BlockKilling.TryGetValue(target.PlayerId, out var isBlocked)) {
Mark = isBlocked ? "(true)" : "(false)";
}*/
//Devourer
if (Devourer.HideNameOfConsumedPlayer.GetBool() && Devourer.playerIdList.Any(x => Main.PlayerStates[x].Role is Devourer { IsEnable: true } dv && dv.PlayerSkinsCosumed.Contains(seer.PlayerId)))
RealName = GetString("DevouredName");
// Camouflage
if ((IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool() && (Main.NormalOptions.MapId != 5 || !Options.CommsCamouflageDisableOnFungle.GetBool())) || Camouflager.IsActive)
RealName = $"<size=0>{RealName}</size> ";
string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) ? $"\n<size=1.7>({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})</size>" : string.Empty;
//Mark・Suffixの適用
var currentText = target.cosmetics.nameText.text;
var changeTo = $"{RealName}{DeathReason}{Mark}\r\n{Suffix}";
bool needUpdate = currentText != changeTo;
if (needUpdate)
{
target.cosmetics.nameText.text = changeTo;
float offset = 0.2f;
if (NameNotifyManager.Notice.TryGetValue(seer.PlayerId, out var notify) && notify.TEXT.Contains('\n'))
{
offset += 0.1f;
}
if (Suffix.ToString() != string.Empty)
{
// If the name is on two lines, the job title text needs to be moved up.
//RoleText.transform.SetLocalY(0.35f);
offset += 0.15f;
}
if (!seer.IsAlive())
{
offset += 0.1f;
}
if (isProgressTextLong)
{
offset += 0.3f;
}
if (Options.CurrentGameMode == CustomGameMode.MoveAndStop)
{
offset += 0.2f;
}
RoleText.transform.SetLocalY(offset);
}
}
else
{
// Restoring the position text coordinates to their initial values
RoleText.transform.SetLocalY(0.2f);
}
}
return Task.CompletedTask;
}
public static void LoversSuicide(byte deathId = 0x7f, bool isExiled = false)
{
if (Main.LoversPlayers.Count == 0) return;
if (Options.LoverSuicide.GetBool() && Main.isLoversDead == false)
{
foreach (PlayerControl loversPlayer in Main.LoversPlayers.ToArray())
{
if (!loversPlayer.Data.IsDead && loversPlayer.PlayerId != deathId) continue;
Main.isLoversDead = true;
foreach (var partnerPlayer in Main.LoversPlayers)
{
if (loversPlayer.PlayerId == partnerPlayer.PlayerId) continue;
if (partnerPlayer.PlayerId != deathId && !partnerPlayer.Data.IsDead)
{
if (partnerPlayer.Is(CustomRoles.Lovers))
{
Main.PlayerStates[partnerPlayer.PlayerId].deathReason = PlayerState.DeathReason.FollowingSuicide;
if (isExiled)
CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, partnerPlayer.PlayerId);
else
partnerPlayer.Kill(partnerPlayer);
}
}
}
}
}
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))]
class PlayerStartPatch
{
public static void Postfix(PlayerControl __instance)
{
var roleText = Object.Instantiate(__instance.cosmetics.nameText);
roleText.transform.SetParent(__instance.cosmetics.nameText.transform);
roleText.transform.localPosition = new(0f, 0.2f, 0f);
roleText.fontSize -= 1.2f;
roleText.text = "RoleText";
roleText.gameObject.name = "RoleText";
roleText.enabled = false;
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))]
class SetColorPatch
{
public static bool IsAntiGlitchDisabled;
public static bool Prefix(PlayerControl __instance, int bodyColor)
{
//色変更バグ対策
if (!AmongUsClient.Instance.AmHost || __instance.CurrentOutfit.ColorId == bodyColor || IsAntiGlitchDisabled) return true;
return true;
}
}
[HarmonyPatch(typeof(Vent), nameof(Vent.ExitVent))]
class ExitVentPatch
{
public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc)
{
Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "ExitVent");
if (!AmongUsClient.Instance.AmHost) return;
Drainer.OnAnyoneExitVent(pc);
Main.PlayerStates[pc.PlayerId].Role.OnExitVent(pc, __instance);
if (Options.WhackAMole.GetBool())
{
_ = new LateTask(() => { pc.TPtoRndVent(); }, 0.5f, "Whack-A-Mole TP");
}
if (!pc.IsModClient() && pc.Is(CustomRoles.Haste))
{
pc.SetKillCooldown(Math.Max(Main.KillTimers[pc.PlayerId], 0.1f));
}
}
}
[HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))]
class EnterVentPatch
{
public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc)
{
Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "EnterVent");
if (pc.GetCustomRole().GetRoleTypes() != RoleTypes.Engineer && !Main.PlayerStates[pc.PlayerId].Role.CanUseImpostorVentButton(pc))
{
pc.MyPhysics?.RpcBootFromVent(__instance.Id);
return;
}
Drainer.OnAnyoneEnterVent(pc, __instance);
Analyzer.OnAnyoneEnterVent(pc);
switch (pc.GetCustomRole())
{
case CustomRoles.Mayor when !Options.UsePets.GetBool() && Main.MayorUsedButtonCount.TryGetValue(pc.PlayerId, out var count2) && count2 < Options.MayorNumOfUseButton.GetInt():
pc.MyPhysics?.RpcBootFromVent(__instance.Id);
pc.ReportDeadBody(null);
break;
}
if (!AmongUsClient.Instance.AmHost) return;
Main.LastEnteredVent.Remove(pc.PlayerId);
Main.LastEnteredVent.Add(pc.PlayerId, __instance);
Main.LastEnteredVentLocation.Remove(pc.PlayerId);
Main.LastEnteredVentLocation.Add(pc.PlayerId, pc.Pos());
if (pc.Is(CustomRoles.Unlucky))
{
var Ue = IRandom.Instance;
if (Ue.Next(0, 100) < Options.UnluckyVentSuicideChance.GetInt())
{
pc.Suicide();
}
}
if (pc.Is(CustomRoles.Damocles))
{
Damocles.OnEnterVent(pc.PlayerId, __instance.Id);
}
Main.PlayerStates[pc.PlayerId].Role.OnEnterVent(pc, __instance);
}
}
[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.CoEnterVent))]
class CoEnterVentPatch
{
public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id)
{
if (!AmongUsClient.Instance.AmHost) return true;
Logger.Info($" {__instance.myPlayer.GetNameWithRole()}, Vent ID: {id}", "CoEnterVent");
if (Main.KillTimers.TryGetValue(__instance.myPlayer.PlayerId, out var timer))
{
Main.KillTimers[__instance.myPlayer.PlayerId] = timer + 0.5f;
}
switch (Options.CurrentGameMode)
{
case CustomGameMode.FFA when FFAManager.FFA_DisableVentingWhenTwoPlayersAlive.GetBool() && Main.AllAlivePlayerControls.Length <= 2:
var pc = __instance.myPlayer;
_ = new LateTask(() =>
{
pc?.Notify(GetString("FFA-NoVentingBecauseTwoPlayers"), 7f);
pc?.MyPhysics?.RpcBootFromVent(id);
}, 0.5f);
return true;
case CustomGameMode.FFA when FFAManager.FFA_DisableVentingWhenKCDIsUp.GetBool() && Main.KillTimers[__instance.myPlayer.PlayerId] <= 0:
_ = new LateTask(() =>
{
__instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseKCDIsUP"), 7f);
__instance.myPlayer?.MyPhysics?.RpcBootFromVent(id);
}, 0.5f);
return true;
case CustomGameMode.MoveAndStop:
case CustomGameMode.HotPotato:
_ = new LateTask(() => { __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); }, 0.5f);
return true;
}
if (Glitch.hackedIdList.ContainsKey(__instance.myPlayer.PlayerId))
{
_ = new LateTask(() =>
{
__instance.myPlayer?.Notify(string.Format(GetString("HackedByGlitch"), "Vent"));
__instance.myPlayer?.MyPhysics?.RpcBootFromVent(id);
}, 0.5f);
return true;
}
if (Penguin.IsVictim(__instance.myPlayer))
{
_ = new LateTask(() => { __instance.myPlayer?.MyPhysics?.RpcBootFromVent(id); }, 0.5f);
return true;
}
if (SoulHunter.IsSoulHunterTarget(__instance.myPlayer.PlayerId))
{
_ = new LateTask(() =>
{
__instance.myPlayer?.Notify(GetString("SoulHunterTargetNotifyNoVent"));
__instance.myPlayer?.MyPhysics?.RpcBootFromVent(id);
_ = new LateTask(() =>
{
if (SoulHunter.IsSoulHunterTarget(__instance.myPlayer.PlayerId)) __instance.myPlayer.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter.GetSoulHunter(__instance.myPlayer.PlayerId).SoulHunter_.GetRealName()), 300f);
}, 4f, log: false);
}, 0.5f);
return true;
}
if (Main.BlockedVents.Contains(id))
{
var pc = __instance.myPlayer;
if (Options.VentguardBlockDoesNotAffectCrew.GetBool() && pc.GetCustomRole().IsCrewmate())
{
}
else
{
_ = new LateTask(() =>
{
pc?.Notify(GetString("EnteredBlockedVent"));
pc?.MyPhysics?.RpcBootFromVent(id);
}, 0.5f);
foreach (var ventguard in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Ventguard)).ToArray())
{
ventguard.Notify(GetString("VentguardNotify"));
}
return true;
}
}
if (__instance.myPlayer.GetCustomRole().GetDYRole() == RoleTypes.Impostor && !Main.PlayerStates[__instance.myPlayer.PlayerId].Role.CanUseImpostorVentButton(__instance.myPlayer))
{
_ = new LateTask(() => { __instance.RpcBootFromVent(id); }, 0.5f);
}
if (Main.NormalOptions.MapId == (int)MapNames.Dleks || ((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer &&
!__instance.myPlayer.CanUseImpostorVentButton()) ||
(__instance.myPlayer.Is(CustomRoles.Mayor) && Main.MayorUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out var count) && count >= Options.MayorNumOfUseButton.GetInt()) ||
(__instance.myPlayer.Is(CustomRoles.Paranoia) && Main.ParaUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out var count2) && count2 >= Options.ParanoiaNumOfUseButton.GetInt()))
&& !__instance.myPlayer.Is(CustomRoles.Nimble))
{
try
{
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable);
writer.WritePacked(127);
AmongUsClient.Instance.FinishRpcImmediately(writer);
_ = new LateTask(() =>
{
try
{
int clientId = __instance.myPlayer.GetClientId();
MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, clientId);
writer2.Write(id);
AmongUsClient.Instance.FinishRpcImmediately(writer2);
}
catch
{
}
}, 0.5f, "Fix DesyncImpostor Stuck");
}
catch
{
}
return true;
}
Main.PlayerStates[__instance.myPlayer.PlayerId].Role.OnCoEnterVent(__instance, id);
return true;
}
}
//[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetName))]
//class SetNamePatch
//{
// public static void Postfix(/*PlayerControl __instance, [HarmonyArgument(0)] string name*/)
// {
// }
//}
[HarmonyPatch(typeof(GameData), nameof(GameData.CompleteTask))]
class GameDataCompleteTaskPatch
{
public static void Postfix(PlayerControl pc /*, uint taskId*/)
{
Logger.Info($"TaskComplete: {pc.GetNameWithRole().RemoveHtmlTags()}", "CompleteTask");
Main.PlayerStates[pc.PlayerId].UpdateTask(pc);
NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))]
class PlayerControlCompleteTaskPatch
{
public static bool Prefix(PlayerControl __instance)
{
return !Workhorse.OnCompleteTask(__instance) && Capitalism.AddTaskForPlayer(__instance); // Cancel task win
}
public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] uint idx)
{
if (__instance != null && __instance.IsAlive()) Benefactor.OnTaskComplete(__instance, __instance.myTasks[(Index)Convert.ToInt32(idx)] as PlayerTask);
if (__instance == null) return;
Snitch.OnCompleteTask(__instance);
var isTaskFinish = __instance.GetTaskState().IsTaskFinished;
if (isTaskFinish && __instance.Is(CustomRoles.Snitch) && __instance.Is(CustomRoles.Madmate))
{
foreach (var impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor)).ToArray())
NameColorManager.Add(impostor.PlayerId, __instance.PlayerId, "#ff1919");
NotifyRoles(SpecifySeer: __instance, ForceLoop: true);
}
if (isTaskFinish &&
__instance.GetCustomRole() is CustomRoles.Doctor or CustomRoles.Sunnyboy or CustomRoles.SpeedBooster)
{
// Execute CustomSyncAllSettings at the end of the task only for matches with sunnyboy, speed booster, or doctor.
MarkEveryoneDirtySettings();
}
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))]
public static class PlayerControlDiePatch
{
public static void Postfix(PlayerControl __instance)
{
if (AmongUsClient.Instance.AmHost) PetsPatch.RpcRemovePet(__instance);
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckSporeTrigger))]
public static class PlayerControlCheckSporeTriggerPatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Mushroom mushroom)
{
Logger.Info($"{__instance.GetNameWithRole()}, mushroom: {mushroom.name} / {mushroom.Id}, at {mushroom.origPosition}", "Spore Trigger");
return !AmongUsClient.Instance.AmHost || !Options.DisableSporeTriggerOnFungle.GetBool();
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckUseZipline))]
public static class PlayerControlCheckUseZiplinePatch
{
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target, [HarmonyArgument(1)] ZiplineBehaviour ziplineBehaviour, [HarmonyArgument(2)] bool fromTop)
{
ziplineBehaviour.downTravelTime = Options.ZiplineTravelTimeFromTop.GetFloat();
ziplineBehaviour.upTravelTime = Options.ZiplineTravelTimeFromBottom.GetFloat();
Logger.Info($"{__instance.GetNameWithRole()}, target: {target.GetNameWithRole()}, {(fromTop ? $"from Top, travel time: {ziplineBehaviour.downTravelTime}s" : $"from Bottom, travel time: {ziplineBehaviour.upTravelTime}s")}", "Zipline Use");
if (AmongUsClient.Instance.AmHost)
{
if (Options.DisableZiplineFromTop.GetBool() && fromTop) return false;
if (Options.DisableZiplineFromUnder.GetBool() && !fromTop) return false;
if (__instance.GetCustomRole().IsImpostor() && Options.DisableZiplineForImps.GetBool()) return false;
if (__instance.GetCustomRole().IsNeutral() && Options.DisableZiplineForNeutrals.GetBool()) return false;
if (__instance.GetCustomRole().IsCrewmate() && Options.DisableZiplineForCrew.GetBool()) return false;
}
return true;
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ProtectPlayer))]
class PlayerControlProtectPlayerPatch
{
public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target)
{
Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "ProtectPlayer");
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RemoveProtection))]
class PlayerControlRemoveProtectionPatch
{
public static void Postfix(PlayerControl __instance)
{
Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}", "RemoveProtection");
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))]
class PlayerControlSetRolePatch
{
public static bool Prefix(PlayerControl __instance, ref RoleTypes roleType)
{
var target = __instance;
var targetName = __instance.GetNameWithRole();
Logger.Info($"{targetName} => {roleType}", "PlayerControl.RpcSetRole");
if (!ShipStatus.Instance.enabled) return true;
if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost)
{
var targetIsKiller = target.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(target.PlayerId);
var ghostRoles = new Dictionary<PlayerControl, RoleTypes>();
foreach (PlayerControl seer in Main.AllPlayerControls)
{
var self = seer.PlayerId == target.PlayerId;
var seerIsKiller = seer.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId);
if (target.Is(CustomRoles.EvilSpirit))
{
ghostRoles[seer] = RoleTypes.GuardianAngel;
}
else if ((self && targetIsKiller) || (!seerIsKiller && target.Is(CustomRoleTypes.Impostor)))
{
ghostRoles[seer] = RoleTypes.ImpostorGhost;
}
else
{
ghostRoles[seer] = RoleTypes.CrewmateGhost;
}
}
if (target.Is(CustomRoles.EvilSpirit))
{
roleType = RoleTypes.GuardianAngel;
}
else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.CrewmateGhost))
{
roleType = RoleTypes.CrewmateGhost;
}
else if (ghostRoles.All(kvp => kvp.Value == RoleTypes.ImpostorGhost))
{
roleType = RoleTypes.ImpostorGhost;
}
else
{
foreach ((PlayerControl seer, RoleTypes role) in ghostRoles)
{
Logger.Info($"Desync {targetName} => {role} for {seer.GetNameWithRole().RemoveHtmlTags()}", "PlayerControl.RpcSetRole");
target.RpcSetRoleDesync(role, seer.GetClientId());
}
return false;
}
}
return true;
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetRole))]
class PlayerControlLocalSetRolePatch
{
public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role)
{
if (!AmongUsClient.Instance.AmHost && !GameStates.IsModHost)
{
var moddedRole = role switch
{
RoleTypes.Impostor => CustomRoles.ImpostorTOHE,
RoleTypes.Shapeshifter => CustomRoles.ShapeshifterTOHE,
RoleTypes.Crewmate => CustomRoles.CrewmateTOHE,
RoleTypes.Engineer => CustomRoles.EngineerTOHE,
RoleTypes.Scientist => CustomRoles.ScientistTOHE,
_ => CustomRoles.NotAssigned,
};
if (moddedRole != CustomRoles.NotAssigned)
{
Main.PlayerStates[__instance.PlayerId].SetMainRole(moddedRole);
}
}
}
}