using System; using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using EHR.Gamemodes; using EHR.Roles; using Hazel; using InnerNet; using Mathf = UnityEngine.Mathf; // ReSharper disable ForCanBeConvertedToForeach namespace EHR.Modules; public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender { public PlayerControl player = player; private static IGameOptions BasedGameOptions => #if IL2CPP Main.RealOptionsData.Restore(new NormalGameOptionsV11(new UnityLogger().CastFast()).CastFast()); #else Main.RealOptionsData.Restore(new NormalGameOptionsV11(new UnityLogger())); #endif protected override bool IsDirty { get; set; } public static void SetDirty(byte playerId) { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender sender && sender.player.PlayerId == playerId) { sender.SetDirty(); break; // Only one sender can have the same player id } } } public static void ForceSendImmediately(byte playerId) { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender sender && sender.player.PlayerId == playerId) { ForceWaitFrame = true; sender.SendGameOptions(); sender.IsDirty = false; break; // Only one sender can have the same player id } } } public static DataFlagRateLimiter.QueuedAction SendAllImmediately() { if (AntiBlackout.SkipTasks && !AntiBlackout.AllowSyncSettings) return null; ForceWaitFrame = true; if (PackedWriterMessages > 0 && PackedWriter != null) { PackedWriter.EndMessage(); var capturedWriter = PackedWriter; DataFlagRateLimiter.Enqueue(() => { AmongUsClient.Instance.SendOrDisconnect(capturedWriter); capturedWriter.Recycle(); }, cleanup: capturedWriter.Recycle); } PackedWriter = MessageWriter.Get(SendOption.Reliable); PackedWriter.StartMessage(26); PackedWriter.WritePacked(AmongUsClient.Instance.GameId); PackedWriterMessages = 0; for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender { IsDirty: true } sender) { sender.SendGameOptions(); sender.IsDirty = false; } } DataFlagRateLimiter.QueuedAction qa = null; if (PackedWriter != null) { if (PackedWriterMessages > 0) { PackedWriter.EndMessage(); Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendAllImmediately"); var capturedWriter = PackedWriter; qa = DataFlagRateLimiter.Enqueue(() => { AmongUsClient.Instance.SendOrDisconnect(capturedWriter); capturedWriter.Recycle(); Logger.Info("PackedWriter flush queue finished and sent", "SendAllImmediately"); }, cleanup: capturedWriter.Recycle); } else { PackedWriter.Recycle(); } } PackedWriter = null; PackedWriterMessages = 0; return qa; } public static void SetDirtyToAll() { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender sender) sender.SetDirty(); } } // For lights call/fix public static void SetDirtyToAllV2() { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && (sender.player.HasDesyncRole() || sender.player.GetCustomRole() is CustomRoles.Transporter or CustomRoles.Lighter or CustomRoles.Doomsayer || sender.player.Is(CustomRoles.Torch) || sender.player.Is(CustomRoles.Mare) || sender.player.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(sender.player.PlayerId))) sender.SetDirty(); } } // For Grenadier blidning/restoring public static void SetDirtyToAllV3() { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && (Grenadier.GrenadierBlinding.Count > 0 && (sender.player.IsImpostor() || sender.player.GetCustomRole().IsNeutral() && Options.GrenadierCanAffectNeutral.GetBool()) || Grenadier.MadGrenadierBlinding.Count > 0 && !sender.player.GetCustomRole().IsImpostorTeam() && !sender.player.Is(CustomRoles.Madmate))) sender.SetDirty(); } } // For players with kill buttons public static void SetDirtyToAllV4() { for (var index = 0; index < AllSenders.Count; index++) { GameOptionsSender allSender = AllSenders[index]; if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && sender.player.CanUseKillButton()) sender.SetDirty(); } } private void SetDirty() { IsDirty = true; } protected override void SendGameOptions() { if (player.AmOwner) { IGameOptions opt = BuildGameOptions(); if (GameManager.Instance?.LogicComponents != null) { foreach (GameLogicComponent com in GameManager.Instance.LogicComponents) { if (com is LogicOptions lo) lo.SetGameOptions(opt); } } GameOptionsManager.Instance.CurrentGameOptions = opt; } else base.SendGameOptions(); } protected override IEnumerator SendGameOptionsAsync() { if (player.AmOwner) { IGameOptions opt = BuildGameOptions(); if (GameManager.Instance?.LogicComponents != null) { foreach (GameLogicComponent com in GameManager.Instance.LogicComponents) { if (com is LogicOptions lo) lo.SetGameOptions(opt); yield return WaitFrameIfNecessary(); } } GameOptionsManager.Instance.CurrentGameOptions = opt; } else yield return base.SendGameOptionsAsync(); } protected override IEnumerator SendOptionsArrayAsync(byte[] optionArray, byte logicOptionsIndex) { if (PackedWriter == null) yield break; if (PackedWriter.Length > 1000 || PackedWriterMessages >= AmongUsClient.Instance.GetMaxMessagePackingLimit()) { PackedWriter.EndMessage(); var qa = DataFlagRateLimiter.Enqueue(() => AmongUsClient.Instance.SendOrDisconnect(PackedWriter)); yield return qa.Wait(); Logger.Info($"PackedWriter flush finished - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArrayAsync"); PackedWriterMessages = 0; if (qa.Dropped) { PackedWriter.Recycle(); PackedWriter = null; Main.Instance.StopCoroutine(ActiveCoroutine); ActiveCoroutine = null; yield return null; yield break; } PackedWriter.Clear(SendOption.Reliable); PackedWriter.StartMessage(26); PackedWriter.WritePacked(AmongUsClient.Instance.GameId); } yield return WaitFrameIfNecessary(); PackedWriterMessages++; PackedWriter.StartMessage(6); { PackedWriter.Write(AmongUsClient.Instance.GameId); PackedWriter.WritePacked(player.OwnerId); PackedWriter.StartMessage(1); { PackedWriter.WritePacked(GameManager.Instance.NetId); PackedWriter.StartMessage(logicOptionsIndex); { PackedWriter.WriteBytesAndSize(optionArray); } PackedWriter.EndMessage(); } PackedWriter.EndMessage(); } PackedWriter.EndMessage(); Logger.Info($"PackedWriter message write complete - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArrayAsync"); } protected override void SendOptionsArray(byte[] optionArray, byte logicOptionsIndex) { if (AntiBlackout.SkipTasks && !AntiBlackout.AllowSyncSettings) return; if (PackedWriter == null) // Single write { Logger.Info("Enqueue complete for single write", "SendOptionsArray"); DataFlagRateLimiter.Enqueue(() => { MessageWriter writer = MessageWriter.Get(SendOption.Reliable); writer.StartMessage(6); { writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(player.OwnerId); writer.StartMessage(1); { writer.WritePacked(GameManager.Instance.NetId); writer.StartMessage(logicOptionsIndex); { writer.WriteBytesAndSize(optionArray); } writer.EndMessage(); } writer.EndMessage(); } writer.EndMessage(); AmongUsClient.Instance.SendOrDisconnect(writer); writer.Recycle(); Logger.Info("Queue finished and sent for single write", "SendOptionsArray"); }); return; } if (PackedWriter.Length > 1000 || PackedWriterMessages >= AmongUsClient.Instance.GetMaxMessagePackingLimit()) { PackedWriter.EndMessage(); Logger.Info($"PackedWriter flush queued - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArray"); var capturedWriter = PackedWriter; DataFlagRateLimiter.Enqueue(() => { AmongUsClient.Instance.SendOrDisconnect(capturedWriter); capturedWriter.Recycle(); Logger.Info("PackedWriter flush queue finished and sent", "SendOptionsArray"); }, cleanup: capturedWriter.Recycle); PackedWriterMessages = 0; PackedWriter = MessageWriter.Get(SendOption.Reliable); PackedWriter.StartMessage(26); PackedWriter.WritePacked(AmongUsClient.Instance.GameId); } PackedWriterMessages++; PackedWriter.StartMessage(6); { PackedWriter.Write(AmongUsClient.Instance.GameId); PackedWriter.WritePacked(player.OwnerId); PackedWriter.StartMessage(1); { PackedWriter.WritePacked(GameManager.Instance.NetId); PackedWriter.StartMessage(logicOptionsIndex); { PackedWriter.WriteBytesAndSize(optionArray); } PackedWriter.EndMessage(); } PackedWriter.EndMessage(); } PackedWriter.EndMessage(); Logger.Info($"PackedWriter message write complete - Length: {PackedWriter.Length}, Messages: {PackedWriterMessages}", "SendOptionsArray"); } public static void RemoveSender(PlayerControl player) { PlayerGameOptionsSender sender = AllSenders.OfType() .FirstOrDefault(sender => sender.player.PlayerId == player.PlayerId); if (sender == null) return; sender.player = null; AllSenders.Remove(sender); } public override IGameOptions BuildGameOptions() { try { Main.RealOptionsData ??= new(GameOptionsManager.Instance.CurrentGameOptions); IGameOptions opt = BasedGameOptions; AURoleOptions.SetOpt(opt); PlayerState state = Main.PlayerStates[player.PlayerId]; opt.BlackOut(state.IsBlackOut); CustomRoles role = player.GetCustomRole(); RoleTypes roleTypes = player.GetRoleTypes(); if (player.inVent && Options.OverrideVisionInVents.GetBool() && Options.InVentVision.TryGetValue(player.GetTeam(), out var option)) { var value = option.GetFloat(); opt.SetFloat(FloatOptionNames.CrewLightMod, value); opt.SetFloat(FloatOptionNames.ImpostorLightMod, value); } switch (Options.CurrentGameMode) { case CustomGameMode.FFA: { if (FreeForAll.FFALowerVisionList.ContainsKey(player.PlayerId)) { opt.SetVision(true); opt.SetFloat(FloatOptionNames.CrewLightMod, FreeForAll.FFALowerVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, FreeForAll.FFALowerVision.GetFloat()); } else SetMaxVision(); break; } case CustomGameMode.CaptureTheFlag: { CaptureTheFlag.ApplyGameOptions(); goto case CustomGameMode.RoomRush; } case CustomGameMode.Snowdown: { Snowdown.ApplyGameOptions(); goto case CustomGameMode.RoomRush; } case CustomGameMode.DoomTag: { DoomTag.ApplyGameOptions(opt, player.PlayerId); break; } case CustomGameMode.NaturalDisasters: { SetMaxVision(); NaturalDisasters.ApplyGameOptions(opt, player.PlayerId); break; } case CustomGameMode.RoomRush when RoomRush.VentLimit.TryGetValue(player.PlayerId, out int vl) && vl > 0: { AURoleOptions.EngineerCooldown = 0.01f; AURoleOptions.EngineerInVentMaxTime = 0f; goto case CustomGameMode.RoomRush; } case CustomGameMode.Mingle: case CustomGameMode.RoomRush: case CustomGameMode.Speedrun: case CustomGameMode.HotPotato: { SetMaxVision(); break; } case CustomGameMode.Deathrace: case CustomGameMode.BedWars: { AURoleOptions.PhantomCooldown = 0.1f; goto case CustomGameMode.RoomRush; } case CustomGameMode.Quiz: { try { AURoleOptions.GuardianAngelCooldown = 900f; AURoleOptions.ProtectionDurationSeconds = 0.01f; } catch (Exception e) { Utils.ThrowException(e); } goto case CustomGameMode.RoomRush; } case CustomGameMode.StopAndGo: { try { AURoleOptions.EngineerCooldown = 1f; AURoleOptions.EngineerInVentMaxTime = 300f; } catch (Exception e) { Utils.ThrowException(e); } goto case CustomGameMode.RoomRush; } case CustomGameMode.HideAndSeek: { CustomHnS.ApplyGameOptions(opt, player); break; } case CustomGameMode.TheMindGame: { try { AURoleOptions.PhantomCooldown = 0.1f; } catch (Exception e) { Utils.ThrowException(e); } goto case CustomGameMode.RoomRush; } case CustomGameMode.KingOfTheZones: case CustomGameMode.SoloPVP: { try { AURoleOptions.GuardianAngelCooldown = 900f; } catch (Exception e) { Utils.ThrowException(e); } goto case CustomGameMode.RoomRush; } case CustomGameMode.Standard: { President.OnAnyoneApplyGameOptions(opt); AURoleOptions.ViperDissolveTime = ImpostorVanillaRoles.ViperDissolveTime.GetFloat(); // can't be desynced float playerSpeed = Main.AllPlayerSpeed.GetValueOrDefault(player.PlayerId); bool frozen = Mathf.Approximately(playerSpeed, Main.MinSpeed); bool inverted = playerSpeed < 0f; foreach (CustomRoles subRole in state.SubRoles) { if (subRole.IsGhostRole()) { AURoleOptions.GuardianAngelCooldown = 5f; continue; } switch (subRole) { case CustomRoles.Watcher: { opt.SetBool(BoolOptionNames.AnonymousVotes, false); break; } case CustomRoles.Flash when !frozen: { Main.AllPlayerSpeed[player.PlayerId] = inverted ? -Options.FlashSpeed.GetFloat() : Options.FlashSpeed.GetFloat(); break; } case CustomRoles.Giant when !frozen: { Main.AllPlayerSpeed[player.PlayerId] = inverted ? -Options.GiantSpeed.GetFloat() : Options.GiantSpeed.GetFloat(); break; } case CustomRoles.Mare when !frozen && Options.MareHasIncreasedSpeed.GetBool(): { Main.AllPlayerSpeed[player.PlayerId] = inverted ? -Options.MareSpeedDuringLightsOut.GetFloat() : Options.MareSpeedDuringLightsOut.GetFloat(); break; } case CustomRoles.Sleep when player.IsAlive() && Utils.IsActive(SystemTypes.Electrical): { SetBlind(); Main.AllPlayerSpeed[player.PlayerId] = Main.MinSpeed; break; } case CustomRoles.Torch: { if (!Utils.IsActive(SystemTypes.Electrical)) { opt.SetVision(true); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.TorchVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.TorchVision.GetFloat()); } else if (!Options.TorchAffectedByLights.GetBool()) { opt.SetVision(true); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.TorchVision.GetFloat() * 5); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.TorchVision.GetFloat() * 5); } break; } case CustomRoles.Bewilder when !Utils.IsActive(SystemTypes.Electrical): { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.BewilderVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.BewilderVision.GetFloat()); break; } case CustomRoles.Sunglasses when !Utils.IsActive(SystemTypes.Electrical): { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.SunglassesVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.SunglassesVision.GetFloat()); break; } case CustomRoles.Reach: { opt.SetInt(Int32OptionNames.KillDistance, 2); break; } case CustomRoles.Constricted: { opt.SetInt(Int32OptionNames.KillDistance, 0); break; } case CustomRoles.Madmate: { opt.SetVision(Options.MadmateHasImpostorVision.GetBool()); break; } case CustomRoles.Lovers when Main.LoversPlayers.Count(x => x.IsAlive()) == 1 && Lovers.LoverDieConsequence.GetValue() == 2: { opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision / 2f); opt.SetFloat(FloatOptionNames.ImpostorFlashlightSize, Main.DefaultImpostorVision / 2f); break; } case CustomRoles.Nimble when roleTypes == RoleTypes.Engineer: { AURoleOptions.EngineerCooldown = Nimble.NimbleCD.GetFloat(); AURoleOptions.EngineerInVentMaxTime = Nimble.NimbleInVentTime.GetFloat(); break; } case CustomRoles.Physicist when roleTypes == RoleTypes.Scientist: { AURoleOptions.ScientistCooldown = Physicist.PhysicistCD.GetFloat(); AURoleOptions.ScientistBatteryCharge = Physicist.PhysicistViewDuration.GetFloat(); break; } case CustomRoles.Finder when roleTypes == RoleTypes.Tracker: { AURoleOptions.TrackerCooldown = Finder.FinderCD.GetFloat(); AURoleOptions.TrackerDuration = Finder.FinderDuration.GetFloat(); AURoleOptions.TrackerDelay = Finder.FinderDelay.GetFloat(); break; } case CustomRoles.Noisy when roleTypes == RoleTypes.Noisemaker: { AURoleOptions.NoisemakerImpostorAlert = Noisy.NoisyImpostorAlert.GetBool(); AURoleOptions.NoisemakerAlertDuration = Noisy.NoisyAlertDuration.GetFloat(); break; } case CustomRoles.Examiner when roleTypes == RoleTypes.Detective: { AURoleOptions.DetectiveSuspectLimit = Examiner.ExaminerSuspectLimit.GetFloat(); break; } } } break; } } switch (player.GetCustomRoleTypes()) { case CustomRoleTypes.Impostor: AURoleOptions.ShapeshifterCooldown = Options.DefaultShapeshiftCooldown.GetFloat(); break; } switch (role) { case CustomRoles.PhantomEHR: AURoleOptions.PhantomCooldown = ImpostorVanillaRoles.PhantomCooldown.GetFloat(); AURoleOptions.PhantomDuration = ImpostorVanillaRoles.PhantomDuration.GetFloat(); break; case CustomRoles.ShapeshifterEHR: AURoleOptions.ShapeshifterCooldown = ImpostorVanillaRoles.ShapeshiftCD.GetFloat(); AURoleOptions.ShapeshifterDuration = ImpostorVanillaRoles.ShapeshiftDur.GetFloat(); break; case CustomRoles.EngineerEHR: AURoleOptions.EngineerCooldown = CrewmateVanillaRoles.EngineerCD.GetFloat(); AURoleOptions.EngineerInVentMaxTime = CrewmateVanillaRoles.EngineerDur.GetFloat(); break; case CustomRoles.NoisemakerEHR: AURoleOptions.NoisemakerImpostorAlert = CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool(); AURoleOptions.NoisemakerAlertDuration = CrewmateVanillaRoles.NoisemakerAlertDuration.GetFloat(); break; case CustomRoles.ScientistEHR: AURoleOptions.ScientistCooldown = CrewmateVanillaRoles.ScientistCD.GetFloat(); AURoleOptions.ScientistBatteryCharge = CrewmateVanillaRoles.ScientistDur.GetFloat(); break; case CustomRoles.TrackerEHR: AURoleOptions.TrackerCooldown = CrewmateVanillaRoles.TrackerCooldown.GetFloat(); AURoleOptions.TrackerDuration = CrewmateVanillaRoles.TrackerDuration.GetFloat(); AURoleOptions.TrackerDelay = CrewmateVanillaRoles.TrackerDelay.GetFloat(); break; case CustomRoles.DetectiveEHR: AURoleOptions.DetectiveSuspectLimit = CrewmateVanillaRoles.DetectiveSuspectLimit.GetFloat(); break; case CustomRoles.JudgeEHR: AURoleOptions.JudgeTaskRequirementPercentage = CrewmateVanillaRoles.JudgeTaskRequirementPercentage.GetFloat(); break; } if (player.UsesJudgeAbilityAsTrigger()) AURoleOptions.JudgeTaskRequirementPercentage = 0f; // When impostor alert is off, and the player is a desync crewmate, set impostor alert as true if (role.IsDesyncRole() && role.IsCrewmate() && !CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool()) AURoleOptions.NoisemakerImpostorAlert = true; else AURoleOptions.NoisemakerImpostorAlert = CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool(); try { if (Shifter.WasShifter.Contains(player.PlayerId) && role.IsImpostor()) opt.SetVision(true); } catch (Exception e) { Utils.ThrowException(e); } try { state.Role.ApplyGameOptions(opt, player.PlayerId); } catch (Exception e) { Utils.ThrowException(e); } if (player.Is(CustomRoles.Bloodlust) && Bloodlust.HasImpVision.GetBool()) opt.SetVision(true); if (!player.Is(CustomRoles.Focused) && Main.CachedAllPlayerControls().Exists(x => x.Is(CustomRoles.Bewilder) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == player.PlayerId && !x.Is(CustomRoles.Hangman))) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.BewilderVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.BewilderVision.GetFloat()); } if (Grenadier.GrenadierBlinding.Count > 0 && (role.IsImpostor() || role.IsNeutral() && Options.GrenadierCanAffectNeutral.GetBool()) || Grenadier.MadGrenadierBlinding.Count > 0 && !role.IsImpostorTeam() && !player.Is(CustomRoles.Madmate)) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Options.GrenadierCauseVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.GrenadierCauseVision.GetFloat()); } switch (role) { case CustomRoles.Alchemist when ((Alchemist)state.Role).VisionPotionActive: opt.SetVision(false); if (Utils.IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, Alchemist.VisionOnLightsOut.GetFloat() * 5); else opt.SetFloat(FloatOptionNames.CrewLightMod, Alchemist.Vision.GetFloat()); break; case CustomRoles.Mayor when Mayor.MayorSeesVoteColorsWhenDoneTasks.GetBool() && player.GetTaskState().IsTaskFinished: opt.SetBool(BoolOptionNames.AnonymousVotes, false); break; } Farmer.OnAnyoneApplyGameOptions(opt, player); Siren.ApplyGameOptionsForOthers(opt, player.PlayerId); Chef.ApplyGameOptionsForOthers(opt, player.PlayerId); Negotiator.OnAnyoneApplyGameOptions(opt, player.PlayerId); Wizard.OnAnyoneApplyGameOptions(opt, player.PlayerId); Curser.OnAnyoneApplyGameOptions(opt, player.PlayerId); Auditor.OnAnyoneApplyGameOptions(opt, player.PlayerId); Clerk.OnAnyoneApplyGameOptions(opt, player.PlayerId); Spider.OnAnyoneApplyGameOptions(opt, player.PlayerId); if (Sprayer.LowerVisionList.Contains(player.PlayerId)) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Sprayer.LoweredVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Sprayer.LoweredVision.GetFloat()); } if (Minion.BlindPlayers.Contains(player.PlayerId)) SetBlind(); if (Slenderman.IsBlinded(player.PlayerId)) SetBlind(); if (Sentinel.IsPatrolling(player.PlayerId)) { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, Sentinel.LoweredVision.GetFloat()); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Sentinel.LoweredVision.GetFloat()); } if (Beacon.IsAffectedPlayer(player.PlayerId)) { opt.SetFloat(FloatOptionNames.CrewLightMod, Beacon.IncreasedVision); opt.SetFloat(FloatOptionNames.ImpostorLightMod, Beacon.IncreasedVision); } Dazzler.SetDazzled(player, opt); Deathpact.SetDeathpactVision(player, opt); Spiritcaller.ReduceVision(opt, player); if (Randomizer.HasSuperVision(player)) SetMaxVision(); else if (Randomizer.IsBlind(player)) SetBlind(); bool energeticIncreaseSpeed = false, energeticDecreaseCooldown = false; if (state.SubRoles.Contains(CustomRoles.Energetic) || Empress.Encouraged != null && Empress.Encouraged.Contains(player.PlayerId)) { if (player.CanUseKillButton()) energeticDecreaseCooldown = true; else switch (roleTypes) { case RoleTypes.Impostor: energeticDecreaseCooldown = true; break; case RoleTypes.Scientist: AURoleOptions.ScientistCooldown *= 0.75f; break; case RoleTypes.Engineer: AURoleOptions.EngineerCooldown *= 0.75f; break; case RoleTypes.Shapeshifter: AURoleOptions.ShapeshifterCooldown *= 0.75f; break; case RoleTypes.Phantom: AURoleOptions.PhantomCooldown *= 0.75f; break; case RoleTypes.Tracker: AURoleOptions.TrackerCooldown *= 0.75f; break; case RoleTypes.Detective: AURoleOptions.DetectiveSuspectLimit *= 1.25f; goto default; case RoleTypes.CrewmateGhost: case RoleTypes.ImpostorGhost: break; default: energeticIncreaseSpeed = true; break; } } if (Magician.BlindPpl.ContainsKey(player.PlayerId)) SetBlind(); if (player.IsCrewmate() && Main.PlayerStates.Values.Any(s => s.Role is Adventurer { IsEnable: true } av && av.ActiveWeapons.Contains(Adventurer.Weapon.Lantern))) SetMaxVision(); if (Chemist.Instances.Any(x => x.IsBlinding && player.PlayerId != x.ChemistPC.PlayerId)) SetBlind(); if (Changeling.ChangedRole.TryGetValue(player.PlayerId, out bool changed) && changed && roleTypes != RoleTypes.Shapeshifter) { AURoleOptions.ShapeshifterCooldown = 300f; AURoleOptions.ShapeshifterDuration = 1f; } if ((Options.UsePhantomBasis.GetBool() || role.AlwaysUsesPhantomBase()) && role.SimpleAbilityTrigger()) AURoleOptions.PhantomDuration = 0.1f; // =================================================================================================================== if (state.IsBlackOut) SetBlind(); AURoleOptions.EngineerCooldown = Mathf.Max(0.01f, AURoleOptions.EngineerCooldown); if (Main.AllPlayerKillCooldown.TryGetValue(player.PlayerId, out float killCooldown)) { if (energeticDecreaseCooldown) killCooldown *= 0.75f; AURoleOptions.KillCooldown = Mathf.Max(0.01f, killCooldown); } if (Main.AllPlayerSpeed.TryGetValue(player.PlayerId, out float speed)) { const float limit = 3f; if (energeticIncreaseSpeed) speed *= 1.25f; if (Mathf.Approximately(speed, 0f)) speed = Main.MinSpeed; if (Camouflage.IsCamouflage && Options.CommsCamouflageSetSameSpeed.GetBool()) speed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); AURoleOptions.PlayerSpeedMod = Mathf.Clamp(speed, -limit, limit); } state.TaskState.HasTasks = Utils.HasTasks(player.Data, false); if (Options.GhostCanSeeOtherVotes.GetBool() && !player.IsAlive()) opt.SetBool(BoolOptionNames.AnonymousVotes, false); if (Options.AdditionalEmergencyCooldown.GetBool() && Options.AdditionalEmergencyCooldownThreshold.GetInt() <= Main.AllAlivePlayerControlsCount) { opt.SetInt( Int32OptionNames.EmergencyCooldown, Options.AdditionalEmergencyCooldownTime.GetInt()); } if (CustomRoles.ClockBlocker.RoleExist(ClockBlocker.CountAddedTimeAfterDeath.GetBool())) { int originalTime = opt.GetInt(Int32OptionNames.EmergencyCooldown); opt.SetInt(Int32OptionNames.EmergencyCooldown, ClockBlocker.GetTotalTime(originalTime)); } if (MeetingStates.FirstMeeting) { int originalTime = opt.GetInt(Int32OptionNames.EmergencyCooldown); opt.SetInt(Int32OptionNames.EmergencyCooldown, originalTime + 30); } if (Options.SyncButtonMode.GetBool() && Options.SyncedButtonCount.GetValue() <= Options.UsedButtonCount) opt.SetInt(Int32OptionNames.EmergencyCooldown, 3600); MeetingTimeManager.ApplyGameOptions(opt); AURoleOptions.ShapeshifterCooldown = Mathf.Max(1f, AURoleOptions.ShapeshifterCooldown); AURoleOptions.ProtectionDurationSeconds = 0f; AURoleOptions.ImpostorsCanSeeProtect = false; Logger.Info($"Updated settings for {player.GetNameWithRole()}: Crew Vision = {opt.GetFloat(FloatOptionNames.CrewLightMod):N2}, Impostor Vision = {opt.GetFloat(FloatOptionNames.ImpostorLightMod):N2}, Speed = {opt.GetFloat(FloatOptionNames.PlayerSpeedMod):N2}", "BuildGameOptions"); return opt; void SetMaxVision() { opt.SetVision(true); opt.SetFloat(FloatOptionNames.CrewLightMod, 1.3f); opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.3f); } void SetBlind() { opt.SetVision(false); opt.SetFloat(FloatOptionNames.CrewLightMod, 0f); opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0f); } } catch (Exception e) { Logger.Error($"Error for {player.GetRealName()} ({player.GetCustomRole()}): {e}", "PlayerGameOptionsSender.BuildGameOptions"); return BasedGameOptions; } } protected override bool AmValid() { return base.AmValid() && player && player.Data && !player.Data.Disconnected && Main.RealOptionsData != null; } }