Files

659 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Gamemodes;
using EHR.Modules;
using EHR.Roles;
using InnerNet;
namespace EHR;
public class PlayerState(byte playerId)
{
public static readonly DeathReason[] AllDeathReason = EnumHelper.GetValues<DeathReason>();
public enum DeathReason
{
Kill,
Vote,
Suicide,
Spell,
Curse,
FollowingSuicide,
Bite,
Poison,
Bombed,
Misfire,
Torched,
Sniped,
Revenge,
Execution,
Disconnected,
Fall,
AFK,
// EHR
Gambled,
Eaten,
Sacrifice,
Quantization,
Overtired,
Ashamed,
Dismembered,
LossOfHead,
Trialed,
Infected,
Demolished,
YinYanged,
Kamikazed,
RNG,
WrongAnswer,
Consumed,
BadLuck,
Asthma,
Assumed,
Negotiation,
Trapped,
Stung,
Scavenged,
Allergy,
Swooped,
Echoed,
Dragged,
Mauled,
WipedOut,
OutOfOxygen,
Retribution,
Taxes,
DidntVote,
SkippedVote,
Deafened,
Patrolled,
Misguess,
LossOfBlood,
Frightened,
Bankrupt,
Bugged,
Destroyed,
Shot,
Diseased,
Stoned,
// Natural Disasters
Meteor,
Lava,
Tornado,
Lightning,
Drowned,
Sunken,
Collapsed,
etc = -1
}
public readonly PlayerControl Player = Utils.GetPlayerById(playerId);
private readonly byte PlayerId = playerId;
public readonly List<CustomRoles> SubRoles = [];
public readonly Dictionary<byte, string> TargetColorData = [];
public CountTypes countTypes = CountTypes.Crew;
public PlainShipRoom LastRoom;
public CustomRoles MainRole = CustomRoles.NotAssigned;
public NetworkedPlayerInfo.PlayerOutfit NormalOutfit;
public (DateTime TimeStamp, byte ID) RealKiller = (DateTime.MinValue, byte.MaxValue);
public RoleBase Role = new VanillaRole();
private int RoleChangeTimes = -1;
public readonly List<CustomRoles> RoleHistory = [];
public bool IsDead { get; set; }
// ReSharper disable once InconsistentNaming
public DeathReason deathReason { get; set; } = DeathReason.etc;
public bool IsBlackOut { get; set; }
public bool IsSuicide => deathReason is DeathReason.Suicide or DeathReason.Fall;
public TaskState TaskState { get; set; } = new();
public void SetMainRole(CustomRoles role)
{
try { Role.Remove(PlayerId); }
catch (Exception e) { Utils.ThrowException(e); }
if (Main.IntroDestroyed && (RoleHistory.Count == 0 || RoleHistory[^1] != MainRole))
RoleHistory.Add(MainRole);
FortuneTeller.OnRoleChange(PlayerId, MainRole, role);
bool previousHasTasks = Utils.HasTasks(Player.Data, false);
countTypes = role.GetCountTypes();
if (CustomTeamManager.GetCustomTeam(PlayerId) != null && !CustomTeamManager.IsSettingEnabledForPlayerTeam(PlayerId, CTAOption.WinWithOriginalTeam))
countTypes = CountTypes.CustomTeam;
SubRoles.ForEach(SetAddonCountTypes);
if (!Player.HasKillButton() && role == CustomRoles.Renegade)
Player.RpcChangeRoleBasis(CustomRoles.Renegade);
Role = role.GetRoleClass();
if (!role.RoleExist(true)) Role.Init();
MainRole = role;
Role.Add(PlayerId);
ExtendedPlayerControl.NameWithRoleCache.Remove(PlayerId);
Logger.Info($"ID {PlayerId} ({Player.GetRealName()}) => {role}, CountTypes => {countTypes}", "SetMainRole");
if (Main.IntroDestroyed && GameStates.InGame)
{
if (!previousHasTasks && Utils.HasTasks(Player.Data, false))
{
Player.RpcResetTasks();
if (!AmongUsClient.Instance.AmHost) LateTask.New(() => TaskState.Init(Player), 1f, log: false);
}
if (role.IsVanilla() || role.IsVanillaEHR())
Main.AbilityUseLimit.Remove(PlayerId);
if (PlayerId == PlayerControl.LocalPlayer.PlayerId && GameStates.IsInTask)
{
HudManager.Instance.SetHudActive(true);
RemoveDisableDevicesPatch.UpdateDisableDevices();
HudSpritePatch.ForceUpdate = true;
}
if (!AmongUsClient.Instance.AmHost) return;
Player.ResetKillCooldown();
if (Decryptor.On) Decryptor.Instances.ForEach(x => x.OnRoleChange(PlayerId));
if (!role.Is(Team.Impostor) && !(role == CustomRoles.Traitor && Traitor.CanGetImpostorOnlyAddons.GetBool()))
SubRoles.ToArray().DoIf(x => x.IsImpOnlyAddon(), RemoveSubRole);
if (role == CustomRoles.GM) return;
if (role is CustomRoles.Sidekick or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Renegade)
SubRoles.ToArray().DoIf(StartGameHostPatch.BasisChangingAddons.ContainsKey, RemoveSubRole);
if (role == CustomRoles.Sidekick && Jackal.Instances.FindFirst(x => x.SidekickId == byte.MaxValue || !x.SidekickId.GetPlayer(), out Jackal jackal))
jackal.SidekickId = PlayerId;
if (Options.CurrentGameMode == CustomGameMode.Standard && GameStates.IsInTask && !AntiBlackout.SkipTasks)
Player.Notify(string.Format(Translator.GetString("RoleChangedNotify"), role.ToColoredString()), 10f);
if (Options.UsePets.GetBool() && Player.CurrentOutfit.PetId == "")
PetsHelper.SetPet(Player, PetsHelper.GetPetId());
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
}
if (!AmongUsClient.Instance.AmHost) return;
CheckMurderPatch.TimeSinceLastKill.Remove(PlayerId);
if (!Main.IntroDestroyed || PlayerControl.LocalPlayer.PlayerId != PlayerId || Options.CurrentGameMode != CustomGameMode.Standard) return;
RoleChangeTimes++;
if (RoleChangeTimes >= 4) Achievements.Type.Transformer.Complete();
}
public void SetSubRole(CustomRoles role, bool replaceAll = false)
{
switch (role)
{
case CustomRoles.Cleansed:
replaceAll = true;
break;
case CustomRoles.BananaMan when Main.IntroDestroyed && !SubRoles.Contains(CustomRoles.BananaMan):
LateTask.New(() => Utils.RpcChangeSkin(Player, new()), 0.2f, log: false);
break;
case CustomRoles.Urgent when !SubRoles.Contains(CustomRoles.Urgent):
Main.NumEmergencyMeetingsUsed[PlayerId]--;
break;
}
if (replaceAll)
{
if (SubRoles.Contains(CustomRoles.Flash) || SubRoles.Contains(CustomRoles.Dynamo) || SubRoles.Contains(CustomRoles.Spurt))
{
Main.AllPlayerSpeed[PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
PlayerGameOptionsSender.SetDirty(PlayerId);
}
SubRoles.Clear();
Utils.SendRPC(CustomRPC.RemoveSubRole, PlayerId, 2);
}
if (!SubRoles.Contains(role))
SubRoles.Add(role);
SetAddonCountTypes(role);
Logger.Info($" ID {PlayerId} ({Player?.GetRealName()}) => {role}, CountTypes => {countTypes}", "SetSubRole");
ExtendedPlayerControl.NameWithRoleCache.Remove(PlayerId);
}
private void SetAddonCountTypes(CustomRoles role)
{
switch (role)
{
case CustomRoles.Bloodlust:
countTypes = CountTypes.Bloodlust;
SubRoles.Remove(CustomRoles.Stressed);
break;
case CustomRoles.Madmate:
countTypes = Options.MadmateCountMode.GetInt() switch
{
0 => CountTypes.OutOfGame,
1 => CountTypes.Impostor,
2 => CountTypes.Crew,
_ => throw new NotImplementedException()
};
SubRoles.Remove(CustomRoles.Entranced);
SubRoles.Remove(CustomRoles.Charmed);
SubRoles.Remove(CustomRoles.Contagious);
SubRoles.Remove(CustomRoles.Rascal);
SubRoles.Remove(CustomRoles.Loyal);
SubRoles.Remove(CustomRoles.Undead);
SubRoles.Remove(CustomRoles.Stressed);
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
break;
case CustomRoles.Charmed:
countTypes = Cultist.CharmedCountMode.GetInt() switch
{
0 => CountTypes.OutOfGame,
1 => CountTypes.Cultist,
2 => countTypes,
_ => throw new NotImplementedException()
};
SubRoles.Remove(CustomRoles.Entranced);
SubRoles.Remove(CustomRoles.Madmate);
SubRoles.Remove(CustomRoles.Contagious);
SubRoles.Remove(CustomRoles.Rascal);
SubRoles.Remove(CustomRoles.Loyal);
SubRoles.Remove(CustomRoles.Undead);
SubRoles.Remove(CustomRoles.Stressed);
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
break;
case CustomRoles.Undead:
countTypes = Necromancer.UndeadCountMode.GetInt() switch
{
0 => CountTypes.OutOfGame,
1 => CountTypes.Necromancer,
2 => countTypes,
_ => throw new NotImplementedException()
};
SubRoles.Remove(CustomRoles.Entranced);
SubRoles.Remove(CustomRoles.Madmate);
SubRoles.Remove(CustomRoles.Contagious);
SubRoles.Remove(CustomRoles.Rascal);
SubRoles.Remove(CustomRoles.Loyal);
SubRoles.Remove(CustomRoles.Charmed);
SubRoles.Remove(CustomRoles.Stressed);
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
break;
case CustomRoles.Entranced:
countTypes = Siren.EntrancedCountMode.GetInt() switch
{
0 => CountTypes.OutOfGame,
1 => CountTypes.Coven,
2 => countTypes,
_ => throw new NotImplementedException()
};
SubRoles.Remove(CustomRoles.Charmed);
SubRoles.Remove(CustomRoles.Madmate);
SubRoles.Remove(CustomRoles.Contagious);
SubRoles.Remove(CustomRoles.Rascal);
SubRoles.Remove(CustomRoles.Loyal);
SubRoles.Remove(CustomRoles.Undead);
SubRoles.Remove(CustomRoles.Stressed);
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
break;
case CustomRoles.LastImpostor:
SubRoles.Remove(CustomRoles.Mare);
break;
case CustomRoles.Contagious:
countTypes = Virus.ContagiousCountMode.GetInt() switch
{
0 => CountTypes.OutOfGame,
1 => CountTypes.Virus,
2 => countTypes,
_ => throw new NotImplementedException()
};
SubRoles.Remove(CustomRoles.Entranced);
SubRoles.Remove(CustomRoles.Madmate);
SubRoles.Remove(CustomRoles.Charmed);
SubRoles.Remove(CustomRoles.Rascal);
SubRoles.Remove(CustomRoles.Loyal);
SubRoles.Remove(CustomRoles.Undead);
SubRoles.Remove(CustomRoles.Stressed);
Utils.NotifyRoles(SpecifySeer: Player);
Utils.NotifyRoles(SpecifyTarget: Player);
break;
}
}
public void RemoveSubRole(CustomRoles role)
{
SubRoles.Remove(role);
if (role is CustomRoles.Flash or CustomRoles.Dynamo or CustomRoles.Spurt)
{
Main.AllPlayerSpeed[PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
PlayerGameOptionsSender.SetDirty(PlayerId);
}
Utils.SendRPC(CustomRPC.RemoveSubRole, PlayerId, 1, (int)role);
ExtendedPlayerControl.NameWithRoleCache.Remove(PlayerId);
}
public void SetDead()
{
IsDead = true;
Main.ForceRebuildCachesPlayerControls();
GameEndChecker.ForceCheckEnd();
if (AmongUsClient.Instance.AmHost)
{
if (Enchanter.EnchantedPlayers != null && Enchanter.EnchantedPlayers.Contains(PlayerId))
deathReason = AllDeathReason[..^8].RandomElement();
RPC.SendDeathReason(PlayerId, deathReason, IsDead);
Utils.CheckAndSpawnAdditionalRenegade(GameData.Instance.GetPlayerById(PlayerId));
if (GameStates.IsMeeting && MeetingHud.Instance.state is MeetingHud.MeetingStates.Discussion or MeetingHud.MeetingStates.NotVoted or MeetingHud.MeetingStates.Voted)
MeetingHud.Instance.CheckForEndVoting();
}
}
public void SetAlive()
{
IsDead = false;
deathReason = DeathReason.etc;
Main.ForceRebuildCachesPlayerControls();
GameEndChecker.ForceCheckEnd();
if (AmongUsClient.Instance.AmHost)
{
RPC.SendDeathReason(PlayerId, deathReason, IsDead);
}
}
public void InitTask(PlayerControl player)
{
TaskState.Init(player);
}
public void UpdateTask(PlayerControl player)
{
TaskState.Update(player);
}
public byte GetRealKiller()
{
return IsDead && RealKiller.TimeStamp != DateTime.MinValue ? RealKiller.ID : byte.MaxValue;
}
public int GetKillCount()
{
int count = 0;
var states = Main.PlayerStates.Values;
foreach (var state in states)
{
if (state.PlayerId == PlayerId) continue;
if (state.GetRealKiller() == PlayerId)
count++;
}
return count;
}
}
public class TaskState
{
public static int InitialTotalTasks;
public int AllTasksCount = -1;
public int CompletedTasksCount;
public bool HasTasks;
public int RemainingTasksCount => AllTasksCount - CompletedTasksCount;
public bool IsTaskFinished => RemainingTasksCount <= 0 && HasTasks;
public void Init(PlayerControl player)
{
Logger.Info($"{player.GetNameWithRole()}: InitTask", "TaskState.Init");
if (!player || player.Data?.Tasks == null) return;
if (!Utils.HasTasks(player.Data, false))
{
AllTasksCount = 0;
return;
}
HasTasks = true;
CompletedTasksCount = 0;
AllTasksCount = player.Data.Tasks.Count;
Logger.Info($"{player.GetNameWithRole()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Init");
}
public void Update(PlayerControl player)
{
try
{
Logger.Info($"{player.GetNameWithRole()}: UpdateTask", "TaskState.Update");
GameData.Instance.RecomputeTaskCounts();
Logger.Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update");
if (Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.Mingle)
player.Notify(Translator.GetString("DoingTasksIsPointlessInThisGameMode"), 10f);
if (AllTasksCount == -1) Init(player);
if (!HasTasks) return;
if (AmongUsClient.Instance.AmHost)
{
Clerk.DidTaskThisRound.Add(player.PlayerId);
bool alive = player.IsAlive();
if (alive && Options.CurrentGameMode == CustomGameMode.Speedrun)
{
if (CompletedTasksCount + 1 >= AllTasksCount)
Speedrun.OnTaskFinish(player);
Speedrun.ResetTimer(player);
}
// Ability Use Gain with this task completed
if (alive && !Main.HasJustStarted)
{
float add = Utils.GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityUseGainWithEachTaskCompleted");
if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0)
{
if (player.Is(CustomRoles.Composter)) add *= Composter.AbilityUseGainMultiplier.GetFloat();
player.RpcIncreaseAbilityUseLimitBy(add);
}
}
try { Main.PlayerStates[player.PlayerId].Role.OnTaskComplete(player, CompletedTasksCount, AllTasksCount); }
catch (Exception e) { Utils.ThrowException(e); }
List<CustomRoles> addons = Main.PlayerStates[player.PlayerId].SubRoles;
if (addons.Contains(CustomRoles.Deadlined)) Deadlined.SetDone(player);
if (addons.Contains(CustomRoles.Stressed)) Stressed.OnTaskComplete(player);
if (addons.Contains(CustomRoles.Unlucky) && alive && IRandom.Instance.Next(0, 100) < Options.UnluckyTaskSuicideChance.GetInt()) player.Suicide();
if (GhostRolesManager.AssignedGhostRoles.TryGetValue(player.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole))
{
if (ghostRole is { Role: CustomRoles.Phantasm, Instance: Phantasm phantasm } && CompletedTasksCount + 1 >= AllTasksCount)
phantasm.OnFinishedTasks(player);
if (ghostRole is { Role: CustomRoles.Haunter, Instance: Haunter haunter })
{
if (CompletedTasksCount + 1 >= AllTasksCount)
haunter.OnFinishedTasks(player);
else if (RemainingTasksCount - 1 <= Haunter.TasksBeforeBeingKnown.GetInt())
haunter.OnOneTaskLeft(player);
}
}
Simon.RemoveTarget(player, Simon.Instruction.Task);
Wyrd.CheckPlayerAction(player, Wyrd.Action.Task);
// Update the player's task count for Task Managers
foreach (PlayerControl pc in Main.CachedAlivePlayerControls())
{
if (pc.Is(CustomRoles.TaskManager) && pc.PlayerId != player.PlayerId)
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player);
}
}
}
catch (Exception e) { Utils.ThrowException(e); }
if (CompletedTasksCount >= AllTasksCount) return;
CompletedTasksCount++;
CompletedTasksCount = Math.Min(AllTasksCount, CompletedTasksCount);
Logger.Info($"{player.GetNameWithRole()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Update");
}
}
public class PlayerVersion(Version ver, string tagStr, string forkId)
{
public readonly string forkId = forkId;
public readonly string tag = tagStr;
public readonly Version version = ver;
public PlayerVersion(string ver, string tagStr, string forkId) : this(Version.Parse(ver), tagStr, forkId) { }
public override bool Equals(object obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == GetType() && Equals((PlayerVersion)obj);
}
private bool Equals(PlayerVersion other)
{
return forkId == other.forkId && tag == other.tag && Equals(version, other.version);
}
public override int GetHashCode()
{
return HashCode.Combine(forkId, tag, version);
}
}
public static class GameStates
{
public enum ServerType
{
Vanilla,
Modded,
Niko,
AOU,
GG,
Local,
Custom
}
public static bool InGame;
public static bool AlreadyDied;
public static bool IsModHost => PlayerControl.LocalPlayer.IsHost() || PlayerControl.AllPlayerControls.ToArray().Any(x => x.IsHost() && x.IsModdedClient());
public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Joined;
public static bool IsInGame => InGame;
public static bool IsEnded => GameEndChecker.Ended || AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Ended;
public static bool IsNotJoined => AmongUsClient.Instance.GameState == InnerNetClient.GameStates.NotJoined;
public static bool IsOnlineGame => AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame;
public static bool IsLocalGame => AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame;
public static bool IsFreePlay => AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay;
public static bool IsInTask => InGame && !MeetingHud.Instance;
public static bool IsMeeting => InGame && MeetingHud.Instance;
public static bool IsVoting => IsMeeting && MeetingHud.Instance.state is MeetingHud.MeetingStates.Voted or MeetingHud.MeetingStates.NotVoted;
public static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown;
public static ServerType CurrentServerType
{
get
{
if (IsFreePlay || IsLocalGame || IsNotJoined) return ServerType.Local;
string regionName = Utils.GetRegionName();
return regionName switch
{
"Local Game" => ServerType.Custom,
"EU" or "NA" or "AS" => ServerType.Vanilla,
"MEU" or "MAS" or "MNA" => ServerType.Modded,
_ when regionName.Contains("Niko", StringComparison.OrdinalIgnoreCase) => ServerType.Niko,
_ when regionName.StartsWith("AOU", StringComparison.OrdinalIgnoreCase) => ServerType.AOU,
_ when regionName.StartsWith("GG", StringComparison.OrdinalIgnoreCase) => ServerType.GG,
_ => ServerType.Custom
};
}
}
public static ServerType CurrentServerTypeInCreateMenu
{
get
{
if (IsFreePlay) return ServerType.Local;
string regionName = Utils.GetRegionName(ignoreNetworkMode: true);
return regionName switch
{
"Local Game" => ServerType.Custom,
"EU" or "NA" or "AS" => ServerType.Vanilla,
"MEU" or "MAS" or "MNA" => ServerType.Modded,
_ when regionName.Contains("Niko", StringComparison.OrdinalIgnoreCase) => ServerType.Niko,
_ when regionName.StartsWith("AOU", StringComparison.OrdinalIgnoreCase) => ServerType.AOU,
_ when regionName.StartsWith("GG", StringComparison.OrdinalIgnoreCase) => ServerType.GG,
_ => ServerType.Custom
};
}
}
/**********TOP ZOOM.cs***********/
public static bool IsShip => ShipStatus.Instance;
public static bool IsCanMove => PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.CanMove;
public static bool IsDead => PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.IsAlive();
}
public static class MeetingStates
{
public static DeadBody[] DeadBodies;
public static int MeetingNum;
public static bool MeetingCalled;
public static bool FirstMeeting = true;
public static bool IsEmergencyMeeting => !ReportTarget;
public static bool IsExistDeadBody => DeadBodies.Length > 0;
public static NetworkedPlayerInfo ReportTarget { get; set; }
}